Skip to content

Commit 9621f9e

Browse files
committed
feat: refresh ops console UI
1 parent 35105d5 commit 9621f9e

13 files changed

Lines changed: 1015 additions & 149 deletions

File tree

apps/web/src/components/CommandPalette/CommandPalette.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,18 @@ export const CommandPalette = ({
3838
>
3939
<div
4040
className="command-palette"
41+
data-testid="command-palette-shell"
4142
role="dialog"
4243
aria-modal="true"
4344
aria-label={locale === "zh" ? "快速操作面板" : "Quick actions palette"}
4445
onMouseDown={(event) => event.stopPropagation()}
4546
>
47+
<div className="command-palette-header">
48+
<span className="section-kicker">{locale === "zh" ? "命令面板" : "Command Palette"}</span>
49+
<span className="command-palette-meta">
50+
{locale === "zh" ? `${actions.length} 项` : `${actions.length} items`}
51+
</span>
52+
</div>
4653
<div className="command-palette-search-row">
4754
<SearchIcon />
4855
<input

apps/web/src/components/Settings/Settings.tsx

Lines changed: 52 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -35,32 +35,50 @@ export const Settings = ({
3535
onSettingsIdlePolicyChange,
3636
onSelectLocale,
3737
t
38-
}: SettingsProps) => (
39-
<main className="settings-route" data-testid="settings-page">
40-
<section className="settings-layout">
41-
<aside className="settings-sidebar-v2">
42-
<nav className="settings-nav-list" aria-label={t("settings")}>
43-
{settingsNavItems(t).map((item) => {
44-
const isActive = item.id === activeSettingsPanel;
45-
return (
46-
<button
47-
key={item.id}
48-
type="button"
49-
className={`settings-nav-item ${isActive ? "active" : ""}`}
50-
onClick={() => onSettingsPanelChange(item.id)}
51-
>
52-
<span className="settings-nav-icon">{item.icon}</span>
53-
<span>{item.label}</span>
54-
</button>
55-
);
56-
})}
57-
</nav>
58-
</aside>
38+
}: SettingsProps) => {
39+
const activePanelLabel = activeSettingsPanel === "general" ? t("settingsGeneral") : t("settingsAppearance");
40+
const languageLabel = locale === "zh" ? "中文" : "English";
5941

60-
<section className="settings-content-v2">
61-
<div className="settings-scroll-panel">
62-
{activeSettingsPanel === "general" ? (
63-
<div className="settings-group-card">
42+
return (
43+
<main className="settings-route" data-testid="settings-page">
44+
<section className="settings-layout">
45+
<aside className="settings-sidebar-v2">
46+
<nav className="settings-nav-list" aria-label={t("settings")}>
47+
{settingsNavItems(t).map((item) => {
48+
const isActive = item.id === activeSettingsPanel;
49+
return (
50+
<button
51+
key={item.id}
52+
type="button"
53+
className={`settings-nav-item ${isActive ? "active" : ""}`}
54+
onClick={() => onSettingsPanelChange(item.id)}
55+
>
56+
<span className="settings-nav-icon">{item.icon}</span>
57+
<span>{item.label}</span>
58+
</button>
59+
);
60+
})}
61+
</nav>
62+
</aside>
63+
64+
<section className="settings-content-v2">
65+
<div className="settings-scroll-panel">
66+
<div className="settings-summary" data-testid="settings-summary">
67+
<div className="settings-summary-item">
68+
<span className="section-kicker">{t("settings")}</span>
69+
<strong>{activePanelLabel}</strong>
70+
</div>
71+
<div className="settings-summary-item">
72+
<span className="section-kicker">{t("launchCommand")}</span>
73+
<strong>{settingsDraft.agentCommand.trim() || t("launchCommandPlaceholder")}</strong>
74+
</div>
75+
<div className="settings-summary-item">
76+
<span className="section-kicker">{t("languageLabel")}</span>
77+
<strong>{languageLabel}</strong>
78+
</div>
79+
</div>
80+
{activeSettingsPanel === "general" ? (
81+
<div className="settings-group-card">
6482
<div className="settings-row">
6583
<div className="settings-row-copy">
6684
<strong>{t("launchCommand")}</strong>
@@ -279,16 +297,17 @@ export const Settings = ({
279297
</div>
280298
</div>
281299
)}
282-
</div>
300+
</div>
283301

284-
<div className="settings-footer-bar">
285-
<div className="settings-page-status">
286-
{t("settingsAutoSave")}
302+
<div className="settings-footer-bar">
303+
<div className="settings-page-status">
304+
{t("settingsAutoSave")}
305+
</div>
287306
</div>
288-
</div>
307+
</section>
289308
</section>
290-
</section>
291-
</main>
292-
);
309+
</main>
310+
);
311+
};
293312

294313
export default Settings;

apps/web/src/components/WorkspaceLaunchOverlay/WorkspaceLaunchOverlay.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,24 @@ export const WorkspaceLaunchOverlay = ({
3535

3636
return (
3737
<div className="overlay" data-testid="overlay">
38-
<div className="modal onboarding-modal">
38+
<div className="modal onboarding-modal launch-overlay-shell" data-testid="launch-overlay-shell">
3939
<div className="onboarding-form">
40-
<div className="onboarding-header">
41-
<h2>{t("localFolder")}</h2>
40+
<div className="onboarding-header launch-overlay-header">
41+
<div className="launch-overlay-copy">
42+
<span className="section-kicker">{t("startWorkspace")}</span>
43+
<h2>{t("localFolder")}</h2>
44+
<p>{t("localFolderHint")}</p>
45+
</div>
46+
<div className="launch-overlay-meta">
47+
<div className="launch-overlay-meta-item">
48+
<span className="section-kicker">{t("selected")}</span>
49+
<strong>{selectedPath || folderBrowser.currentPath || t("loading")}</strong>
50+
</div>
51+
<div className="launch-overlay-meta-item">
52+
<span className="section-kicker">{target.type === "wsl" ? "WSL" : t("nativeTarget")}</span>
53+
<strong>{target.type === "wsl" ? (target.distro?.trim() || (t("nativeTarget") === "Native" ? "Default distro" : "默认发行版")) : t("nativeTarget")}</strong>
54+
</div>
55+
</div>
4256
</div>
4357
<div className="choice-grid">
4458
<div className="choice active" data-testid="choice-local-only">
@@ -47,7 +61,7 @@ export const WorkspaceLaunchOverlay = ({
4761
</div>
4862
</div>
4963
{canUseWsl && (
50-
<div className="choice-grid small">
64+
<div className="choice-grid small launch-overlay-runtime">
5165
<div className={`choice ${target.type === "native" ? "active" : ""}`} onClick={() => onUpdateTarget({ type: "native" })}>
5266
<strong>{t("nativeTarget")}</strong>
5367
<div className="hint">{t("nativeTargetHint")}</div>

apps/web/src/components/WorktreeModal/WorktreeModal.tsx

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,29 +27,50 @@ export const WorktreeModal = ({
2727
t
2828
}: WorktreeModalProps) => (
2929
<div className="modal-overlay">
30-
<div className="modal-card">
31-
<div className="modal-header">
32-
<h3>{worktree.name}</h3>
33-
<button className="btn tiny" onClick={onClose}>{t("close")}</button>
30+
<div className="modal-card worktree-modal-card" role="dialog" aria-modal="true" data-testid="worktree-modal">
31+
<div className="modal-header worktree-modal-header">
32+
<div className="worktree-modal-copy">
33+
<span className="section-kicker">{locale === "zh" ? "工作树检查" : "Worktree Inspect"}</span>
34+
<h3>{worktree.name}</h3>
35+
</div>
36+
<div className="worktree-modal-meta">
37+
<span className="worktree-meta-chip">{worktree.branch}</span>
38+
<span className="worktree-meta-chip" title={worktree.path}>{worktree.path}</span>
39+
<span className={`worktree-meta-chip status ${worktree.status ? "dirty" : "clean"}`}>
40+
{worktree.status || t("clean")}
41+
</span>
42+
<button className="btn tiny ghost" type="button" onClick={onClose}>{t("close")}</button>
43+
</div>
3444
</div>
35-
<div className="file-tabs">
45+
<div className="file-tabs worktree-modal-tabs">
3646
{(["status", "diff", "tree"] as const).map((nextView) => (
37-
<div
47+
<button
3848
key={nextView}
49+
type="button"
3950
className={`t-tab ${view === nextView ? "active" : ""}`}
4051
onClick={() => onViewChange(nextView)}
52+
aria-pressed={view === nextView}
4153
>
4254
{nextView === "status" ? t("statusTab") : nextView === "diff" ? t("diff") : t("treeTab")}
43-
</div>
55+
</button>
4456
))}
4557
</div>
4658
<div className="modal-body">
4759
{worktree.loading && <div className="empty">{t("loadingWorktreeDetails")}</div>}
4860
{view === "status" && (
49-
<div>
50-
<div className="muted">{t("path")}: {worktree.path}</div>
51-
<div className="muted">{t("branch")}: {worktree.branch}</div>
52-
<div className="status">{worktree.status || t("clean")}</div>
61+
<div className="worktree-status-view">
62+
<div className="worktree-status-row">
63+
<span className="section-kicker">{t("path")}</span>
64+
<strong>{worktree.path}</strong>
65+
</div>
66+
<div className="worktree-status-row">
67+
<span className="section-kicker">{t("branch")}</span>
68+
<strong>{worktree.branch}</strong>
69+
</div>
70+
<div className="worktree-status-row">
71+
<span className="section-kicker">{t("statusTab")}</span>
72+
<strong>{worktree.status || t("clean")}</strong>
73+
</div>
5374
</div>
5475
)}
5576
{view === "diff" && (

apps/web/src/components/workspace/WorkspaceEditorPanel.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ export const WorkspaceEditorPanel = ({
5353
>
5454
<div className="panel-inner workspace-code-panel">
5555
<div className="workspace-code-header">
56+
<div className="workspace-panel-title-block workspace-code-title-block">
57+
<span className="section-kicker">{t("codePanel")}</span>
58+
<strong>{previewFileName || t("selectFileFromNavigator")}</strong>
59+
</div>
5660
<div className="workspace-code-modes">
5761
{isExpanded ? (
5862
<>
@@ -78,12 +82,7 @@ export const WorkspaceEditorPanel = ({
7882
</span>
7983
)}
8084
</>
81-
) : (
82-
<div className="workspace-code-title-block">
83-
<span className="section-kicker">{t("codePanel")}</span>
84-
<strong>{previewFileName || t("selectFileFromNavigator")}</strong>
85-
</div>
86-
)}
85+
) : null}
8786
</div>
8887
<div className="workspace-code-actions">
8988
<div className="workspace-search-shell" ref={searchShellRef}>

apps/web/src/components/workspace/WorkspaceShell.tsx

Lines changed: 21 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,17 @@
11
import type { ReactNode } from "react";
2-
import type { Locale, Translator } from "../../i18n";
3-
import {
4-
WorkspaceBranchIcon,
5-
WorkspaceChangesIcon,
6-
WorkspaceCodeIcon,
7-
WorkspaceFolderIcon,
8-
WorkspaceTerminalIcon
9-
} from "../icons";
2+
import type { Translator } from "../../i18n";
3+
import type { WorkspaceShellSummaryItem } from "../../features/workspace/workspace-shell-summary";
4+
import { WorkspaceCodeIcon, WorkspaceTerminalIcon } from "../icons";
105

116
type WorkspaceShellProps = {
12-
locale: Locale;
137
isFocusMode: boolean;
148
isCodeExpanded: boolean;
159
showAgentPanel: boolean;
1610
showCodePanel: boolean;
1711
showTerminalPanel: boolean;
1812
rightSplit: number;
19-
workspaceFolderName: string;
20-
branchName: string;
21-
changeCount: number;
13+
statusItems: WorkspaceShellSummaryItem[];
14+
runtimeHint: string;
2215
statusBanner?: ReactNode;
2316
agentPanel: ReactNode;
2417
codePanel: ReactNode;
@@ -28,16 +21,14 @@ type WorkspaceShellProps = {
2821
};
2922

3023
export const WorkspaceShell = ({
31-
locale,
3224
isFocusMode,
3325
isCodeExpanded,
3426
showAgentPanel,
3527
showCodePanel,
3628
showTerminalPanel,
3729
rightSplit,
38-
workspaceFolderName,
39-
branchName,
40-
changeCount,
30+
statusItems,
31+
runtimeHint,
4132
statusBanner,
4233
agentPanel,
4334
codePanel,
@@ -46,47 +37,37 @@ export const WorkspaceShell = ({
4637
t
4738
}: WorkspaceShellProps) => (
4839
<main className="workspace-shell">
49-
<div className="workspace-main-header workspace-shell-header">
50-
<div className="workspace-main-header-copy">
51-
<div className="workspace-main-meta">
52-
<span className="workspace-main-chip">
53-
<WorkspaceFolderIcon />
54-
<span>{workspaceFolderName}</span>
55-
</span>
56-
<span className="workspace-main-chip">
57-
<WorkspaceBranchIcon />
58-
<span>{branchName || "—"}</span>
59-
</span>
60-
<span className="workspace-main-chip">
61-
<WorkspaceChangesIcon />
62-
<span>{t("changesCount", { count: changeCount })}</span>
63-
</span>
64-
</div>
40+
<div className="workspace-status-strip" data-testid="workspace-status-strip">
41+
<div className="workspace-status-strip-items">
42+
{statusItems.map((item) => (
43+
<div key={item.key} className={`workspace-status-item tone-${item.tone ?? "neutral"}`}>
44+
<span className="workspace-status-label">{item.label}</span>
45+
<strong className="workspace-status-value">{item.value}</strong>
46+
</div>
47+
))}
6548
</div>
66-
<div className="workspace-main-actions">
49+
<div className="workspace-status-strip-actions">
6750
<button
6851
type="button"
69-
className={`workspace-panel-toggle ${showCodePanel ? "active" : ""}`}
52+
className={`workspace-panel-toggle icon-only ${showCodePanel ? "active" : ""}`}
7053
onClick={() => onToggleRightPane("code")}
7154
title={t("codePanel")}
7255
aria-pressed={showCodePanel}
56+
aria-label={t("codePanel")}
7357
>
7458
<WorkspaceCodeIcon />
75-
<span>{t("codePanel")}</span>
7659
</button>
7760
<button
7861
type="button"
79-
className={`workspace-panel-toggle ${showTerminalPanel ? "active" : ""}`}
62+
className={`workspace-panel-toggle icon-only ${showTerminalPanel ? "active" : ""}`}
8063
onClick={() => onToggleRightPane("terminal")}
8164
title={t("terminalPanel")}
8265
aria-pressed={showTerminalPanel}
66+
aria-label={t("terminalPanel")}
8367
>
8468
<WorkspaceTerminalIcon />
85-
<span>{t("terminalPanel")}</span>
8669
</button>
87-
<span className="workspace-shortcut-hint">
88-
{locale === "zh" ? "⌘/Ctrl+K 快速操作" : "⌘/Ctrl+K actions"}
89-
</span>
70+
<span className="workspace-shortcut-hint">{runtimeHint}</span>
9071
</div>
9172
</div>
9273

0 commit comments

Comments
 (0)