feat: glass ui pass

This commit is contained in:
Brooklyn Nicholson
2026-05-16 19:21:33 -05:00
parent 062eed654d
commit d67a438fec
104 changed files with 5173 additions and 1919 deletions
+16 -8
View File
@@ -2,7 +2,6 @@ import { useStore } from '@nanostores/react'
import type { CSSProperties, ReactNode } from 'react'
import { useSyncExternalStore } from 'react'
import { Backdrop } from '@/components/Backdrop'
import { PaneShell } from '@/components/pane-shell'
import { SidebarProvider } from '@/components/ui/sidebar'
import {
@@ -21,9 +20,11 @@ import { TitlebarControls, type TitlebarTool } from './titlebar-controls'
interface AppShellProps {
children: ReactNode
commandCenterOpen?: boolean
leftStatusbarItems?: readonly StatusbarItem[]
leftTitlebarTools?: readonly TitlebarTool[]
onOpenSettings: () => void
onOpenSearch: () => void
overlays?: ReactNode
statusbarItems?: readonly StatusbarItem[]
titlebarTools?: readonly TitlebarTool[]
@@ -46,9 +47,11 @@ const viewportIsFullscreen = () =>
export function AppShell({
children,
commandCenterOpen = false,
leftStatusbarItems,
leftTitlebarTools,
onOpenSettings,
onOpenSearch,
overlays,
statusbarItems,
titlebarTools
@@ -70,9 +73,9 @@ export function AppShell({
? 0
: titlebarControls.left + TITLEBAR_HEIGHT + Math.round(TITLEBAR_HEIGHT / 2)
// The static system cluster (file-browser, haptics, settings) is hardcoded
// in TitlebarControls. Pane-supplied tools (preview's group) render in a
// separate cluster anchored further left.
// The static system cluster (haptics, profiles, settings, right-sidebar) is
// hardcoded in TitlebarControls. Pane-supplied tools (preview's group) render
// in a separate cluster anchored further left.
//
// Width math has to include the `gap-x-1` (0.25rem) between buttons:
// N buttons + (N - 1) inner gaps, plus one extra 0.25rem of breathing room
@@ -105,7 +108,7 @@ export function AppShell({
return (
<SidebarProvider
className="h-screen min-h-0 bg-background"
className="h-screen min-h-0 flex-col bg-background"
onOpenChange={setSidebarOpen}
open={sidebarOpen}
style={
@@ -127,10 +130,15 @@ export function AppShell({
} as CSSProperties
}
>
<TitlebarControls leftTools={leftTitlebarTools} onOpenSettings={onOpenSettings} tools={titlebarTools} />
<TitlebarControls
commandCenterOpen={commandCenterOpen}
leftTools={leftTitlebarTools}
onOpenSearch={onOpenSearch}
onOpenSettings={onOpenSettings}
tools={titlebarTools}
/>
<Backdrop />
<main className="relative z-3 flex h-screen w-full flex-col overflow-hidden pr-0.75 pt-0.75 transition-none">
<main className="relative z-3 flex min-h-0 w-full flex-1 flex-col overflow-hidden bg-(--glass-chat-surface-background) transition-none">
<PaneShell className="min-h-0 flex-1">
<div
aria-hidden="true"
@@ -3,16 +3,14 @@ import { useMemo } from 'react'
import type { CommandCenterSection } from '@/app/command-center'
import { GatewayMenuPanel } from '@/app/shell/gateway-menu-panel'
import { Activity, AlertCircle, Clock, Command, Cpu, FolderOpen, GitBranch, Hash, Loader2, Sparkles } from '@/lib/icons'
import { Activity, AlertCircle, Clock, Command, Cpu, Hash, Loader2, Sparkles } from '@/lib/icons'
import type { RuntimeReadinessResult } from '@/lib/runtime-readiness'
import { compactPath, contextBarLabel, LiveDuration, usageContextLabel } from '@/lib/statusbar'
import { contextBarLabel, LiveDuration, usageContextLabel } from '@/lib/statusbar'
import { cn } from '@/lib/utils'
import { $desktopActionTasks } from '@/store/activity'
import { $previewServerRestartStatus } from '@/store/preview'
import {
$busy,
$currentBranch,
$currentCwd,
$currentModel,
$currentProvider,
$currentUsage,
@@ -30,7 +28,6 @@ import type { StatusbarItem } from '../statusbar-controls'
interface StatusbarItemsOptions {
agentsOpen: boolean
browseSessionCwd: () => Promise<void>
commandCenterOpen: boolean
extraLeftItems: readonly StatusbarItem[]
extraRightItems: readonly StatusbarItem[]
@@ -45,7 +42,6 @@ interface StatusbarItemsOptions {
export function useStatusbarItems({
agentsOpen,
browseSessionCwd,
commandCenterOpen,
extraLeftItems,
extraRightItems,
@@ -58,8 +54,6 @@ export function useStatusbarItems({
toggleCommandCenter
}: StatusbarItemsOptions) {
const busy = useStore($busy)
const currentBranch = useStore($currentBranch)
const currentCwd = useStore($currentCwd)
const currentModel = useStore($currentModel)
const currentProvider = useStore($currentProvider)
const currentUsage = useStore($currentUsage)
@@ -79,10 +73,10 @@ export function useStatusbarItems({
const gatewayMenuContent = useMemo(
() => (
<GatewayMenuPanel
logLines={gatewayLogLines}
onOpenSystem={() => openCommandCenterSection('system')}
gatewayState={gatewayState}
inferenceStatus={inferenceStatus}
logLines={gatewayLogLines}
onOpenSystem={() => openCommandCenterSection('system')}
statusSnapshot={statusSnapshot}
/>
),
@@ -108,6 +102,7 @@ export function useStatusbarItems({
const gatewayConnecting = gatewayState === 'connecting'
const inferenceReady = gatewayOpen && inferenceStatus?.ready === true
const gatewayDegraded = gatewayOpen || gatewayConnecting
const gatewayDetail = gatewayOpen
? inferenceStatus?.ready
? 'ready'
@@ -117,6 +112,7 @@ export function useStatusbarItems({
: gatewayConnecting
? 'connecting'
: 'offline'
const gatewayClassName = inferenceReady
? undefined
: gatewayDegraded
@@ -277,31 +273,12 @@ export function useStatusbarItems({
title: currentProvider ? `Switch model · ${currentProvider}: ${currentModel || ''}` : 'Open model picker',
variant: 'action'
},
{
icon: <FolderOpen className="size-3" />,
id: 'cwd',
label: currentCwd ? compactPath(currentCwd) : 'No project cwd',
onSelect: () => void browseSessionCwd(),
title: currentCwd ? `Change working directory · ${currentCwd}` : 'Choose working directory',
variant: 'action'
},
{
hidden: !currentBranch,
icon: <GitBranch className="size-3" />,
id: 'branch',
label: currentBranch,
title: currentBranch ? `Current branch: ${currentBranch}` : undefined,
variant: 'text'
},
versionItem
],
[
browseSessionCwd,
busy,
contextBar,
contextUsage,
currentBranch,
currentCwd,
currentModel,
currentProvider,
sessionStartedAt,
+3 -6
View File
@@ -2,20 +2,17 @@ import type * as React from 'react'
import { cn } from '@/lib/utils'
interface SidebarPanelLabelProps extends React.ComponentProps<'span'> {
dotClassName?: string
}
type SidebarPanelLabelProps = React.ComponentProps<'span'>
export function SidebarPanelLabel({ children, className, dotClassName, ...props }: SidebarPanelLabelProps) {
export function SidebarPanelLabel({ children, className, ...props }: SidebarPanelLabelProps) {
return (
<span
className={cn(
'flex min-w-0 items-center gap-2 text-[0.64rem] font-semibold uppercase tracking-[0.16em] text-sidebar-foreground/72',
'flex min-w-0 items-center gap-1.5 text-[0.6875rem] font-medium text-sidebar-foreground/55',
className
)}
{...props}
>
<span aria-hidden="true" className={cn('dither inline-block size-2 shrink-0 rounded-[1px]', dotClassName)} />
<span className="min-w-0 truncate leading-none">{children}</span>
</span>
)
@@ -49,7 +49,7 @@ export function StatusbarControls({ className, leftItems = [], items = [], ...pr
return (
<footer
className={cn(
'flex h-7 shrink-0 items-stretch justify-between gap-2 border-t border-border/55 bg-[color-mix(in_srgb,var(--dt-muted)_45%,var(--dt-card))] px-1 py-0 text-muted-foreground/95 [-webkit-app-region:no-drag]',
'flex h-5 shrink-0 items-stretch justify-between gap-2 border-t border-(--ui-stroke-tertiary) bg-(--glass-sidebar-surface-background) px-1 py-0 text-(--ui-text-tertiary) [-webkit-app-region:no-drag]',
className
)}
{...props}
@@ -89,7 +89,7 @@ function StatusbarItemView({ item, navigate }: { item: StatusbarItem; navigate:
<DropdownMenuTrigger asChild>
<button
className={cn(
'inline-flex h-full cursor-pointer items-center gap-1 rounded-none px-1.5 text-[0.68rem] text-muted-foreground/95 transition-colors hover:bg-(--chrome-action-hover) hover:text-foreground disabled:cursor-default disabled:opacity-45',
'inline-flex h-full cursor-pointer items-center gap-1 rounded-none px-1.5 text-[0.6875rem] text-(--ui-text-tertiary) transition-colors hover:bg-(--chrome-action-hover) hover:text-foreground disabled:cursor-default disabled:opacity-45',
item.className
)}
disabled={item.disabled}
@@ -150,7 +150,7 @@ function StatusbarItemView({ item, navigate }: { item: StatusbarItem; navigate:
return (
<div
className={cn(
'inline-flex h-full items-center gap-1 px-1.5 text-[0.68rem] text-muted-foreground/90',
'inline-flex h-full items-center gap-1 px-1.5 text-[0.6875rem] text-(--ui-text-tertiary)',
item.className
)}
>
@@ -163,7 +163,7 @@ function StatusbarItemView({ item, navigate }: { item: StatusbarItem; navigate:
return (
<a
className={cn(
'inline-flex h-full cursor-pointer items-center gap-1 rounded-none px-1.5 text-[0.68rem] text-muted-foreground/95 transition-colors hover:bg-(--chrome-action-hover) hover:text-foreground disabled:cursor-default disabled:opacity-45',
'inline-flex h-full cursor-pointer items-center gap-1 rounded-none px-1.5 text-[0.6875rem] text-(--ui-text-tertiary) transition-colors hover:bg-(--chrome-action-hover) hover:text-foreground disabled:cursor-default disabled:opacity-45',
item.className
)}
href={item.href}
@@ -179,7 +179,7 @@ function StatusbarItemView({ item, navigate }: { item: StatusbarItem; navigate:
return (
<button
className={cn(
'inline-flex h-full cursor-pointer items-center gap-1 rounded-none px-1.5 text-[0.68rem] text-muted-foreground/95 transition-colors hover:bg-(--chrome-action-hover) hover:text-foreground disabled:cursor-default disabled:opacity-45',
'inline-flex h-full cursor-pointer items-center gap-1 rounded-none px-1.5 text-[0.6875rem] text-(--ui-text-tertiary) transition-colors hover:bg-(--chrome-action-hover) hover:text-foreground disabled:cursor-default disabled:opacity-45',
item.className
)}
disabled={item.disabled}
@@ -2,6 +2,7 @@ import { useStore } from '@nanostores/react'
import type { ComponentProps, ReactNode } from 'react'
import { useNavigate } from 'react-router-dom'
import { Codicon } from '@/components/ui/codicon'
import {
DropdownMenu,
DropdownMenuContent,
@@ -11,7 +12,7 @@ import {
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu'
import { triggerHaptic } from '@/lib/haptics'
import { FolderOpen, NotebookTabs, Settings, Users, Volume2, VolumeX } from '@/lib/icons'
import { Volume2, VolumeX } from '@/lib/icons'
import { cn } from '@/lib/utils'
import { $hapticsMuted, toggleHapticsMuted } from '@/store/haptics'
import { $fileBrowserOpen, $sidebarOpen, toggleFileBrowserOpen, toggleSidebarOpen } from '@/store/layout'
@@ -40,10 +41,18 @@ export type SetTitlebarToolGroup = (id: string, tools: readonly TitlebarTool[],
interface TitlebarControlsProps extends ComponentProps<'div'> {
leftTools?: readonly TitlebarTool[]
tools?: readonly TitlebarTool[]
commandCenterOpen?: boolean
onOpenSettings: () => void
onOpenSearch: () => void
}
export function TitlebarControls({ leftTools = [], tools = [], onOpenSettings }: TitlebarControlsProps) {
export function TitlebarControls({
leftTools = [],
tools = [],
commandCenterOpen = false,
onOpenSettings,
onOpenSearch
}: TitlebarControlsProps) {
const navigate = useNavigate()
const hapticsMuted = useStore($hapticsMuted)
const fileBrowserOpen = useStore($fileBrowserOpen)
@@ -63,7 +72,7 @@ export function TitlebarControls({ leftTools = [], tools = [], onOpenSettings }:
const leftToolbarTools: TitlebarTool[] = [
{
icon: <NotebookTabs />,
icon: <Codicon name="layout-sidebar-left" />,
id: 'sidebar',
label: sidebarOpen ? 'Hide sidebar' : 'Show sidebar',
onSelect: () => {
@@ -71,21 +80,33 @@ export function TitlebarControls({ leftTools = [], tools = [], onOpenSettings }:
toggleSidebarOpen()
}
},
{
active: commandCenterOpen,
icon: <Codicon name="search" />,
id: 'search',
label: 'Search',
onSelect: () => {
triggerHaptic('open')
onOpenSearch()
},
title: 'Search sessions, views, and actions'
},
...leftTools
]
const rightSidebarTool: TitlebarTool = {
active: fileBrowserOpen,
icon: <Codicon name="layout-sidebar-right" />,
id: 'right-sidebar',
label: fileBrowserOpen ? 'Hide right sidebar' : 'Show right sidebar',
onSelect: () => {
triggerHaptic('tap')
toggleFileBrowserOpen()
}
}
// Static system tools — always pinned to the screen's right edge.
const systemTools: TitlebarTool[] = [
{
active: fileBrowserOpen,
icon: <FolderOpen />,
id: 'file-browser',
label: fileBrowserOpen ? 'Hide file browser' : 'Show file browser',
onSelect: () => {
triggerHaptic('tap')
toggleFileBrowserOpen()
}
},
{
active: hapticsMuted,
icon: hapticsMuted ? <VolumeX /> : <Volume2 />,
@@ -94,7 +115,7 @@ export function TitlebarControls({ leftTools = [], tools = [], onOpenSettings }:
onSelect: toggleHaptics
},
{
icon: <Settings />,
icon: <Codicon name="settings-gear" />,
id: 'settings',
label: 'Open settings',
onSelect: () => {
@@ -113,7 +134,7 @@ export function TitlebarControls({ leftTools = [], tools = [], onOpenSettings }:
<>
<div
aria-label="Window controls"
className="fixed left-(--titlebar-controls-left) top-(--titlebar-controls-top) z-70 flex translate-y-[2px] flex-row items-center gap-x-1 pointer-events-auto select-none [-webkit-app-region:no-drag]"
className="fixed left-(--titlebar-controls-left) top-(--titlebar-controls-top) z-70 flex translate-y-0.5 flex-row items-center gap-x-1 pointer-events-auto select-none [-webkit-app-region:no-drag]"
>
{leftToolbarTools
.filter(tool => !tool.hidden)
@@ -150,6 +171,7 @@ export function TitlebarControls({ leftTools = [], tools = [], onOpenSettings }:
))}
<ProfilesMenuButton navigate={navigate} />
{settingsTool && <TitlebarToolButton navigate={navigate} tool={settingsTool} />}
<TitlebarToolButton navigate={navigate} tool={rightSidebarTool} />
</div>
</>
)
@@ -169,7 +191,7 @@ function ProfilesMenuButton({ navigate }: { navigate: ReturnType<typeof useNavig
title="Profiles"
type="button"
>
<Users />
<Codicon name="account" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-64" sideOffset={8}>
@@ -186,7 +208,7 @@ function ProfilesMenuButton({ navigate }: { navigate: ReturnType<typeof useNavig
navigate(PROFILES_ROUTE)
}}
>
<Users className="size-4" />
<Codicon name="account" size="1rem" />
<span>Manage profiles</span>
</DropdownMenuItem>
</DropdownMenuContent>
@@ -198,6 +220,7 @@ function TitlebarToolButton({ navigate, tool }: { navigate: ReturnType<typeof us
const className = cn(
titlebarButtonClass,
'grid place-items-center bg-transparent select-none [&_svg]:size-4',
tool.active && 'bg-(--ui-bg-tertiary)! text-foreground!',
tool.className
)
+3 -3
View File
@@ -13,13 +13,13 @@ export const TITLEBAR_FALLBACK_WINDOW_BUTTON_X = 24
export const TITLEBAR_EDGE_INSET = 14
export const titlebarButtonClass =
'h-[var(--titlebar-control-height)] w-[var(--titlebar-control-size)] rounded-md text-muted-foreground hover:bg-accent hover:text-foreground'
'h-[var(--titlebar-control-height)] w-[var(--titlebar-control-size)] rounded-md text-muted-foreground/85 transition-colors hover:bg-(--ui-bg-tertiary) hover:text-foreground'
export const titlebarHeaderBaseClass =
'pointer-events-none relative z-3 flex h-(--titlebar-height) shrink-0 items-center justify-start gap-3 bg-background/70 px-[max(0.75rem,var(--titlebar-content-inset,0px))] backdrop-blur-sm'
'pointer-events-none relative z-3 flex h-(--titlebar-height) shrink-0 items-center justify-start gap-3 border-b border-(--ui-stroke-tertiary) bg-(--glass-chat-surface-background) px-[max(0.75rem,var(--titlebar-content-inset,0rem))]'
export const titlebarHeaderShadowClass =
"shadow-header after:pointer-events-none after:absolute after:left-0 after:right-0 after:top-full after:h-10 after:bg-linear-to-b after:from-background after:via-background/80 after:to-transparent after:content-['']"
"after:pointer-events-none after:absolute after:left-0 after:right-0 after:top-full after:h-4 after:bg-linear-to-b after:from-(--glass-chat-surface-background) after:to-transparent after:content-['']"
export function titlebarControlsPosition(
windowButtonPosition: HermesConnection['windowButtonPosition'] | undefined,