Skip to content

Commit f0dd437

Browse files
committed
refactor: simplify UI component patterns and polish styling
This commit overhauls several UI patterns across the codebase: 1. Replace text-based action buttons with icon-only ShellIconButton across session, workbench, shell header and composer areas, add appropriate tooltips 2. Restructure session card actions into a dropdown menu instead of inline delete button 3. Add tooltips for timestamps, top bar metadata and message markers 4. Clean up unused imports and types (remove FocusedThemeToken, DropdownMenuSeparator/Label) 5. Fix runtime preview server config and add node modules symlinking for runtime template 6. Refactor message card logic to use better role-based labeling with tooltips 7. Add comprehensive CSS custom property theming for shell UI components 8. Update DESIGN.md to document preferred primitive usage patterns
1 parent a208d58 commit f0dd437

15 files changed

Lines changed: 295 additions & 145 deletions

File tree

apps/agent-html-app/DESIGN.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
具体要求:
6161

6262
- `Button``Input``Textarea``Tabs``Badge``Card``ScrollArea` 等交互 primitive 直接使用 shadcn 生成的组件。
63+
- `Tooltip``DropdownMenu``ContextMenu``Popover` 等收纳型 primitive 也优先使用 shadcn / Radix 入口,把低频操作和解释性内容收纳起来。
6364
- 不复制一份自制基础控件来替代 shadcn。
6465
- 需要变化时,优先通过 token、variant、组合方式、语义 wrapper 调整。
6566
- feature 层允许封装 `ShellActionButton``ShellPaneHeader` 这类语义组件,但底层 primitive 仍应来自 shadcn。
@@ -357,6 +358,7 @@ header 默认只包含:
357358
- 页面层不新增解释性 `description`、caption、help copy
358359
- 标题优先用名词或短动作
359360
- 状态优先用短标签
361+
- 冗余说明、低频动作、实现细节优先移入 `tooltip``dropdown menu``context menu`,不常驻上屏
360362
- 文案必须看起来像产品语言,而不是实现日志
361363

362364
### 7.2 不应作为主内容的信息

apps/agent-html-app/src/components/ui/dropdown-menu.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ const DropdownMenu = DropdownMenuPrimitive.Root
77
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
88
const DropdownMenuGroup = DropdownMenuPrimitive.Group
99
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
10-
const DropdownMenuSeparator = DropdownMenuPrimitive.Separator
11-
const DropdownMenuLabel = DropdownMenuPrimitive.Label
1210

1311
function DropdownMenuContent({
1412
className,
@@ -98,7 +96,6 @@ export {
9896
DropdownMenuGroup,
9997
DropdownMenuItem,
10098
DropdownMenuLabel,
101-
DropdownMenuSeparator,
10299
DropdownMenuShortcut,
103100
DropdownMenuTrigger,
104101
}

apps/agent-html-app/src/features/app-shell/components/top-bar.tsx

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1-
import { FileCode2Icon } from "lucide-react"
1+
import {
2+
FileCode2Icon,
3+
MonitorIcon,
4+
TriangleAlertIcon,
5+
} from "lucide-react"
26

37
import {
48
ShellStatusBadge,
5-
ShellSectionLabel,
69
} from "@/features/app-shell/components/shell-content"
10+
import {
11+
Tooltip,
12+
TooltipContent,
13+
TooltipTrigger,
14+
} from "@/components/ui/tooltip"
715
import { isTauriRuntime } from "@/lib/tauri"
816
import type { WorkbenchView } from "@/lib/types"
917

@@ -22,22 +30,47 @@ export function TopBar({
2230
<header className="app-shell-topbar">
2331
<div className="app-shell-topbar-row">
2432
<div className="app-shell-topbar-group">
25-
<FileCode2Icon className="app-shell-inline-icon app-shell-brand-icon" />
26-
<div className="app-shell-topbar-brand">
27-
<span className="app-shell-topbar-copy">agent-html</span>
28-
<span className="app-shell-panel-title">review studio</span>
29-
</div>
33+
<Tooltip>
34+
<TooltipTrigger asChild>
35+
<div className="app-shell-topbar-brand">
36+
<FileCode2Icon className="app-shell-inline-icon app-shell-brand-icon" />
37+
<span className="app-shell-topbar-copy">agent-html</span>
38+
</div>
39+
</TooltipTrigger>
40+
<TooltipContent side="bottom">review studio</TooltipContent>
41+
</Tooltip>
3042
</div>
3143
<div className="app-shell-topbar-center">
3244
<p className="app-shell-session-title">{sessionName}</p>
3345
</div>
3446
<div className="app-shell-status-group">
35-
<ShellStatusBadge
36-
label={isTauriRuntime() ? "desktop" : "local"}
37-
variant="outline"
38-
/>
39-
<ShellSectionLabel>{activeView}</ShellSectionLabel>
40-
{hasError ? <ShellStatusBadge label="error" variant="destructive" /> : null}
47+
<ShellStatusBadge label={activeView} variant="outline" />
48+
<Tooltip>
49+
<TooltipTrigger asChild>
50+
<span
51+
aria-label={isTauriRuntime() ? "Desktop runtime" : "Local runtime"}
52+
className="app-shell-topbar-meta-icon"
53+
>
54+
<MonitorIcon className="app-shell-inline-icon" />
55+
</span>
56+
</TooltipTrigger>
57+
<TooltipContent side="bottom">
58+
{isTauriRuntime() ? "desktop runtime" : "local runtime"}
59+
</TooltipContent>
60+
</Tooltip>
61+
{hasError ? (
62+
<Tooltip>
63+
<TooltipTrigger asChild>
64+
<span
65+
aria-label="Error"
66+
className="app-shell-topbar-meta-icon app-shell-topbar-meta-icon-error"
67+
>
68+
<TriangleAlertIcon className="app-shell-inline-icon" />
69+
</span>
70+
</TooltipTrigger>
71+
<TooltipContent side="bottom">error</TooltipContent>
72+
</Tooltip>
73+
) : null}
4174
</div>
4275
</div>
4376
</header>

apps/agent-html-app/src/features/sessions/components/session-card.tsx

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,26 @@
1-
import { Trash2Icon } from "lucide-react"
1+
import { EllipsisIcon, Trash2Icon } from "lucide-react"
22

33
import { Button } from "@/components/ui/button"
44
import {
5-
ShellActionGroup,
6-
ShellIconButton,
5+
DropdownMenu,
6+
DropdownMenuContent,
7+
DropdownMenuItem,
8+
DropdownMenuTrigger,
9+
} from "@/components/ui/dropdown-menu"
10+
import {
711
ShellMetaRow,
812
ShellSessionStatusBadge,
913
ShellSplitRow,
1014
} from "@/features/app-shell/components/shell-content"
11-
import { formatSessionTimestampLabel } from "@/lib/time"
15+
import {
16+
Tooltip,
17+
TooltipContent,
18+
TooltipTrigger,
19+
} from "@/components/ui/tooltip"
20+
import {
21+
formatSessionTimestampLabel,
22+
formatTimestampLabel,
23+
} from "@/lib/time"
1224
import type { SessionSummary } from "@/lib/types"
1325
import { cn } from "@/lib/utils"
1426

@@ -27,6 +39,9 @@ export function SessionCard({
2739
onDelete,
2840
onOpen,
2941
}: SessionCardProps) {
42+
const shortTimestamp = formatSessionTimestampLabel(session.updatedAt)
43+
const fullTimestamp = formatTimestampLabel(session.updatedAt)
44+
3045
return (
3146
<section
3247
className={cn(
@@ -56,22 +71,37 @@ export function SessionCard({
5671
</ShellSplitRow>
5772
<ShellMetaRow
5873
action={
59-
<ShellActionGroup>
60-
{!active ? (
61-
<ShellIconButton
62-
ariaLabel="Delete session"
63-
className="app-shell-session-card-action"
64-
disabled={disabled}
65-
onClick={() => onDelete(session.id)}
66-
size="icon-xs"
67-
variant="ghost"
68-
>
69-
<Trash2Icon data-icon="inline-start" />
70-
</ShellIconButton>
71-
) : null}
72-
</ShellActionGroup>
74+
!active ? (
75+
<DropdownMenu>
76+
<DropdownMenuTrigger asChild>
77+
<Button
78+
aria-label="Session actions"
79+
className="app-shell-session-card-action"
80+
disabled={disabled}
81+
size="icon-xs"
82+
type="button"
83+
variant="ghost"
84+
>
85+
<EllipsisIcon data-icon="inline-start" />
86+
</Button>
87+
</DropdownMenuTrigger>
88+
<DropdownMenuContent align="end">
89+
<DropdownMenuItem onClick={() => onDelete(session.id)}>
90+
<Trash2Icon />
91+
Delete
92+
</DropdownMenuItem>
93+
</DropdownMenuContent>
94+
</DropdownMenu>
95+
) : null
96+
}
97+
copy={
98+
<Tooltip>
99+
<TooltipTrigger asChild>
100+
<span>{shortTimestamp}</span>
101+
</TooltipTrigger>
102+
<TooltipContent side="bottom">{fullTimestamp}</TooltipContent>
103+
</Tooltip>
73104
}
74-
copy={formatSessionTimestampLabel(session.updatedAt)}
75105
/>
76106
</div>
77107
</section>

apps/agent-html-app/src/features/sessions/components/session-rail-header.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export function SessionRailHeader({
4040
className="border-0"
4141
disabled={disabled}
4242
onClick={onCreateSession}
43+
tooltip="New session"
4344
>
4445
<PlusIcon data-icon="inline-start" />
4546
</ShellIconButton>

apps/agent-html-app/src/features/sessions/components/session-rail.tsx

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import { useMemo, useState } from "react"
2-
import { Settings2Icon } from "lucide-react"
32

43
import type { SessionSummary } from "@/lib/types"
54

65
import {
76
ShellEmptyCard,
8-
ShellIconButton,
97
ShellLoadingRow,
108
ShellSectionLabel,
119
ShellPaneScaffold,
@@ -104,15 +102,6 @@ export function SessionRail({
104102
{loading ? <ShellLoadingRow>Load</ShellLoadingRow> : null}
105103
</ShellScrollSurface>
106104
}
107-
footer={
108-
<ShellIconButton
109-
ariaLabel="Session settings"
110-
disabled={disabled}
111-
variant="ghost"
112-
>
113-
<Settings2Icon data-icon="inline-start" />
114-
</ShellIconButton>
115-
}
116105
/>
117106
)
118107
}

apps/agent-html-app/src/features/shell/components/message-card.tsx

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1+
import { BotIcon, UserIcon } from "lucide-react"
2+
13
import type { AgentShellMessage } from "@/lib/types"
24

35
import {
46
ShellSectionLabel,
5-
ShellStatusBadge,
67
} from "@/features/app-shell/components/shell-content"
8+
import {
9+
Tooltip,
10+
TooltipContent,
11+
TooltipTrigger,
12+
} from "@/components/ui/tooltip"
713
import { cn } from "@/lib/utils"
814
import { MessageBody } from "./message-body"
915

@@ -14,23 +20,11 @@ function getMessageLines(message: AgentShellMessage): string[] {
1420
.filter(Boolean)
1521
}
1622

17-
function getMessageLabel(message: AgentShellMessage): string {
18-
if (message.role === "user") {
19-
return "You"
20-
}
21-
22-
if (message.proposalSnapshot) {
23-
return "Proposal"
24-
}
25-
26-
return "Review"
27-
}
28-
2923
function getMessageTone(message: AgentShellMessage): "default" | "secondary" {
3024
return message.role === "user" ? "secondary" : "default"
3125
}
3226

33-
function getMessageTitle(message: AgentShellMessage): string {
27+
function getMessageTitle(message: AgentShellMessage): string | undefined {
3428
if (message.proposalSnapshot) {
3529
return getMessageLines(message)[0] ?? "Proposal"
3630
}
@@ -39,7 +33,7 @@ function getMessageTitle(message: AgentShellMessage): string {
3933
return "Context"
4034
}
4135

42-
return "Note"
36+
return undefined
4337
}
4438

4539
function getMessageItems(message: AgentShellMessage): string[] {
@@ -64,6 +58,12 @@ type MessageCardProps = {
6458

6559
export function MessageCard({ message }: MessageCardProps) {
6660
const proposal = Boolean(message.proposalSnapshot)
61+
const title = getMessageTitle(message)
62+
const genericMessage = !proposal && !title
63+
const markerIcon = message.role === "user"
64+
? <UserIcon className="app-shell-inline-icon" />
65+
: <BotIcon className="app-shell-inline-icon" />
66+
const markerLabel = message.role === "user" ? "Your note" : "Review note"
6767

6868
return (
6969
<section
@@ -76,14 +76,21 @@ export function MessageCard({ message }: MessageCardProps) {
7676
: "app-shell-message-card app-shell-message-card-agent",
7777
)}
7878
>
79-
<div className="app-shell-split-row">
80-
<p className="app-shell-message-heading">{getMessageTitle(message)}</p>
81-
{proposal ? (
82-
<ShellSectionLabel>Proposal</ShellSectionLabel>
83-
) : (
84-
<ShellStatusBadge label={getMessageLabel(message)} variant="outline" />
85-
)}
86-
</div>
79+
{!genericMessage ? (
80+
<div className="app-shell-split-row">
81+
<p className="app-shell-message-heading">{title}</p>
82+
{proposal ? <ShellSectionLabel>Proposal</ShellSectionLabel> : null}
83+
</div>
84+
) : (
85+
<Tooltip>
86+
<TooltipTrigger asChild>
87+
<div aria-label={markerLabel} className="app-shell-message-marker">
88+
{markerIcon}
89+
</div>
90+
</TooltipTrigger>
91+
<TooltipContent side="right">{markerLabel}</TooltipContent>
92+
</Tooltip>
93+
)}
8794
<MessageBody
8895
items={getMessageItems(message)}
8996
text={getMessageText(message)}

apps/agent-html-app/src/features/shell/components/shell-composer.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import { SendHorizontalIcon } from "lucide-react"
2+
13
import {
2-
ShellActionButton,
4+
ShellIconButton,
35
ShellStatusBadge,
46
} from "@/features/app-shell/components/shell-content"
57
import { ComposerField } from "./composer-field"
@@ -28,18 +30,20 @@ export function ShellComposer({
2830
<ComposerField
2931
disabled={interactionLocked}
3032
onChange={(event) => onDraftChange(event.target.value)}
31-
placeholder="Note"
33+
placeholder="Reply"
3234
value={draft}
3335
/>
3436
</div>
35-
<ShellActionButton
36-
className="app-shell-plain-action app-shell-composer-send"
37+
<ShellIconButton
38+
ariaLabel="Send note"
39+
className="app-shell-plain-icon app-shell-composer-send"
3740
disabled={!draft.trim() || interactionLocked}
3841
onClick={onSend}
42+
tooltip="Send"
3943
variant="ghost"
4044
>
41-
Send
42-
</ShellActionButton>
45+
<SendHorizontalIcon data-icon="inline-start" />
46+
</ShellIconButton>
4347
</div>
4448
)
4549
}

0 commit comments

Comments
 (0)