diff --git a/src/web/server_helpers.cpp b/src/web/server_helpers.cpp index b443d6419..a23342df0 100644 --- a/src/web/server_helpers.cpp +++ b/src/web/server_helpers.cpp @@ -606,6 +606,18 @@ bool WebServer::Impl::session_model_deleted(const std::string& model_name) const return true; } +namespace { + +void append_worktree_session(json& target, const WorktreeSessionInfo& worktree) { + if (!worktree.active()) return; + target["worktree"] = { + {"name", worktree.worktree_name}, + {"branch", worktree.worktree_branch}, + }; +} + +} // namespace + json WebServer::Impl::session_info_to_json(const SessionInfo& s, const SessionMeta* m) const { json o; const std::string model_name = @@ -646,6 +658,7 @@ json WebServer::Impl::session_info_to_json(const SessionInfo& s, const SessionMe token_usage_has_values(s.session_token_usage) ? s.session_token_usage : (m ? m->session_token_usage : TokenUsage{})); + if (m) append_worktree_session(o, m->worktree); if (m && !m->todos.empty()) { o["todos"] = todo_items_to_json(m->todos); o["todo_summary"] = todo_summary_to_json(m->todos); @@ -684,14 +697,9 @@ json WebServer::Impl::session_meta_to_json(const SessionMeta& m, const std::stri o["permission_mode"] = m.permission_mode.empty() ? "default" : m.permission_mode; o["token_usage"] = token_usage_or_null(m.last_token_usage); o["session_token_usage"] = token_usage_or_null(m.session_token_usage); - // 会话的 worktree 状态(add-webui-git-session-pill):前端 pill 在会话 - // 开始后据此显示只读 worktree 徽标。inactive 时省略字段。 - if (m.worktree.active()) { - o["worktree"] = { - {"name", m.worktree.worktree_name}, - {"branch", m.worktree.worktree_branch}, - }; - } + // 会话的 worktree 状态(add-webui-git-session-pill):前端 pill 与侧栏 + // 据此恢复只读状态。inactive 时省略字段。 + append_worktree_session(o, m.worktree); if (!m.todos.empty()) { o["todos"] = todo_items_to_json(m.todos); o["todo_summary"] = todo_summary_to_json(m.todos); diff --git a/tests/web/web_server_smoke_test.cpp b/tests/web/web_server_smoke_test.cpp index 0423c1c3e..4b93b1099 100644 --- a/tests/web/web_server_smoke_test.cpp +++ b/tests/web/web_server_smoke_test.cpp @@ -861,6 +861,41 @@ TEST(WebServerHttp, CreateSessionThenListShowsActive) { EXPECT_EQ(occurrences, 1) << "active 与 disk meta 必须合并为同一条"; } +// 场景:active session 走 SessionInfo 序列化,也必须保留持久化 meta 中的 +// worktree 状态,否则侧栏刷新后当前会话会丢失 worktree 标识。 +TEST(WebServerHttp, ActiveWorktreeSessionListIncludesWorktreeState) { + WebServerFixture fx; + auto post = cpr::Post(cpr::Url{fx.url("/api/sessions")}, + cpr::Header{{"Content-Type", "application/json"}}, + cpr::Body{R"({})"}); + ASSERT_EQ(post.status_code, 201) << post.text; + const auto sid = json::parse(post.text)["session_id"].get(); + + auto* entry = fx.registry->lookup(sid); + ASSERT_NE(entry, nullptr); + ASSERT_NE(entry->sm, nullptr); + EXPECT_EQ(entry->sm->ensure_active_session_id(), sid); + + acecode::WorktreeSessionInfo worktree; + worktree.original_cwd = fx.cwd; + worktree.worktree_path = fx.cwd + "/.acecode/worktrees/ses-active-list"; + worktree.worktree_name = "ses-active-list"; + worktree.worktree_branch = "worktree-ses-active-list"; + entry->sm->set_active_worktree(worktree); + + auto list = cpr::Get(cpr::Url{fx.url("/api/sessions")}); + ASSERT_EQ(list.status_code, 200) << list.text; + const auto sessions = json::parse(list.text); + const auto found = std::find_if(sessions.begin(), sessions.end(), [&](const auto& item) { + return item.value("id", std::string{}) == sid; + }); + ASSERT_NE(found, sessions.end()); + EXPECT_TRUE((*found)["active"].get()); + ASSERT_TRUE(found->contains("worktree")); + EXPECT_EQ((*found)["worktree"]["name"], "ses-active-list"); + EXPECT_EQ((*found)["worktree"]["branch"], "worktree-ses-active-list"); +} + TEST(WebServerHttp, SessionUserMessageSearchFindsTextAndAttachmentNames) { WebServerFixture fx; diff --git a/web/public/vs-icons/Worktree.svg b/web/public/vs-icons/Worktree.svg new file mode 100644 index 000000000..b11092cdc --- /dev/null +++ b/web/public/vs-icons/Worktree.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/src/components/ChatView.jsx b/web/src/components/ChatView.jsx index 0c75f281c..355377042 100644 --- a/web/src/components/ChatView.jsx +++ b/web/src/components/ChatView.jsx @@ -1659,6 +1659,15 @@ export function ChatView({ sessionRef, sessionId, onSessionPromoted, onCommandWo await sendInputOrBuiltin(id, sendPayload); if (worktreeIntent) { setLocalWorktree({ sid: id, name: `ses-${id}` }); + const noWorkspace = !!created?.target?.noWorkspace; + notifySessionListChanged({ + reason: 'worktree-created', + sessionId: id, + workspaceHash: noWorkspace + ? '' + : (created?.response?.workspace_hash || created?.target?.hash || ''), + noWorkspace, + }); } } if (payload.text.trim()) recordInputHistory(payload.text); @@ -1703,6 +1712,15 @@ export function ChatView({ sessionRef, sessionId, onSessionPromoted, onCommandWo .then(() => { if (sessionWorktreeIntent) { setLocalWorktree({ sid: targetSid, name: `ses-${targetSid}` }); + const noWorkspace = !!(ref?.noWorkspace || ref?.no_workspace); + notifySessionListChanged({ + reason: 'worktree-created', + sessionId: targetSid, + workspaceHash: noWorkspace + ? '' + : (ref?.workspaceHash || ref?.workspace_hash || ''), + noWorkspace, + }); } if (payload.text.trim()) recordInputHistory(payload.text); if (!ref?.title) setTranscriptTitle(payload.text || composerAttachments[0]?.name || '附件消息'); diff --git a/web/src/components/DesktopContextMenu.jsx b/web/src/components/DesktopContextMenu.jsx index 2229ffe56..1d59ff0fa 100644 --- a/web/src/components/DesktopContextMenu.jsx +++ b/web/src/components/DesktopContextMenu.jsx @@ -50,6 +50,7 @@ const ACTION_LABELS = { [DESKTOP_CONTEXT_ACTIONS.COLLAPSE_DIRECTORY]: '折叠目录', [DESKTOP_CONTEXT_ACTIONS.COPY_PREVIEW_TEXT]: '复制预览内容', [DESKTOP_CONTEXT_ACTIONS.COPY_PREVIEW_METADATA]: '复制预览信息', + [DESKTOP_CONTEXT_ACTIONS.COPY_PREVIEW_IMAGE]: '复制图片', [DESKTOP_CONTEXT_ACTIONS.COPY_FILE_DIFF]: '复制此文件 diff', [DESKTOP_CONTEXT_ACTIONS.COPY_ALL_DIFFS]: '复制全部 diff', [DESKTOP_CONTEXT_ACTIONS.LOCATE_IN_FILE_TREE]: '在文件树中定位', @@ -182,7 +183,7 @@ async function copyTextWithToast(text, label = '已复制') { export async function copyImageWithToast(target) { const result = await copyImageToSystemClipboard(target?.copyImageUrl || target?.previewUrl || '', { - mimeType: target?.mimeType || '', + mimeType: target?.mimeType || target?.contentType || '', }); if (result?.ok) { toast({ kind: 'ok', text: '已复制图片' }); @@ -298,6 +299,7 @@ async function runAction(item, target, rememberedText = '', rememberedSelectionC await copyTextWithToast(actionTarget?.url || actionTarget?.path || ''); break; case DESKTOP_CONTEXT_ACTIONS.COPY_ATTACHMENT_IMAGE: + case DESKTOP_CONTEXT_ACTIONS.COPY_PREVIEW_IMAGE: await copyImageWithToast(actionTarget); break; case DESKTOP_CONTEXT_ACTIONS.ADD_SELECTION_CONTEXT: diff --git a/web/src/components/FilePreviewContent.jsx b/web/src/components/FilePreviewContent.jsx index c6a37f669..ec219b405 100644 --- a/web/src/components/FilePreviewContent.jsx +++ b/web/src/components/FilePreviewContent.jsx @@ -205,6 +205,7 @@ export function FilePreviewContent({ api, cwd, path, wrapPreview, onToggleWrapPr 'data-desktop-preview-kind': state.kind || undefined, 'data-desktop-preview-size': Number.isFinite(state.size) ? String(state.size) : undefined, 'data-desktop-preview-content-type': state.contentType || undefined, + 'data-desktop-preview-copy-image-url': state.kind === 'image' ? state.previewUrl || undefined : undefined, }; if (state.kind === 'image') { return ( @@ -218,7 +219,11 @@ export function FilePreviewContent({ api, cwd, path, wrapPreview, onToggleWrapPr > {path} - setImagePreview(null)} /> + setImagePreview(null)} + /> ); } diff --git a/web/src/components/Icon.jsx b/web/src/components/Icon.jsx index 19ee290da..78dd4d61a 100644 --- a/web/src/components/Icon.jsx +++ b/web/src/components/Icon.jsx @@ -66,6 +66,7 @@ const ICONS = { warning: 'StatusWarning', wordWrap: 'WordWrap', world: 'World', + worktree: 'Worktree', error: 'StatusError', }; diff --git a/web/src/components/ImageLightbox.jsx b/web/src/components/ImageLightbox.jsx index f4639f025..1c72d098e 100644 --- a/web/src/components/ImageLightbox.jsx +++ b/web/src/components/ImageLightbox.jsx @@ -1,7 +1,7 @@ import { createPortal } from 'react-dom'; import { VsIcon } from './Icon.jsx'; -export function ImageLightbox({ preview, onClose }) { +export function ImageLightbox({ preview, onClose, contextMenuAttrs }) { if (!preview?.src) return null; return createPortal( @@ -24,6 +24,7 @@ export function ImageLightbox({ preview, onClose }) { src={preview.src} alt={preview.alt || ''} className="max-w-full max-h-full object-contain shadow-xl" + {...contextMenuAttrs} onClick={(event) => event.stopPropagation()} /> , diff --git a/web/src/components/SettingsPage.jsx b/web/src/components/SettingsPage.jsx index 835b24868..936f208cd 100644 --- a/web/src/components/SettingsPage.jsx +++ b/web/src/components/SettingsPage.jsx @@ -89,6 +89,7 @@ const NAV = [ { key: 'archived', label: '已归档会话', icon: 'archive' }, { key: 'usage', label: '使用情况', icon: 'list' }, { key: 'feedback', label: '问题反馈', icon: 'help' }, + { key: 'about', label: '关于', icon: 'info' }, ]; const DEFAULT_UPGRADE_SERVICE_URL = 'http://2017studio.imwork.net:82/aupdate/'; @@ -196,7 +197,7 @@ export function SettingsPage({ onFontSizeChange={onFontSizeChange} /> )} - {activeNavKey === 'config' && } + {activeNavKey === 'config' && } {activeNavKey === 'personalization' && } {activeNavKey === 'skills' && } {activeNavKey === 'mcp' && } @@ -207,6 +208,7 @@ export function SettingsPage({ {activeNavKey === 'archived' && } {activeNavKey === 'usage' && } {activeNavKey === 'feedback' && } + {activeNavKey === 'about' && } @@ -462,16 +464,72 @@ function SectionAppearance({ theme, setTheme, fontSize, onFontSizeChange }) { ); } -// ─── 配置 ────────────────────────────────────────────────────────────────── -// 真实接入:升级服务 URL。其余程序版本 / 依赖项 / 诊断 / 重置仍保留占位。 +// ─── 关于 ────────────────────────────────────────────────────────────────── -function SectionConfig({ health }) { +function SectionAbout({ health }) { + const [webCoreInfo, setWebCoreInfo] = useState(null); + + useEffect(() => { + let cancelled = false; + getCurrentWebCoreInfo() + .then((info) => { + if (!cancelled) setWebCoreInfo(info); + }) + .catch(() => { + if (!cancelled) setWebCoreInfo(null); + }); + return () => { cancelled = true; }; + }, []); + + const programVersionLabel = formatProgramVersion(health?.version); + const webCoreLabel = formatWebCoreLabel(webCoreInfo); + const webCoreDetail = formatWebCoreDetail(webCoreInfo); + + return ( + <> +

关于

+ + {/* 程序版本 */} +
+
+
当前版本
+
ACECode 桌面 / TUI / Daemon 同版本号
+
+ {programVersionLabel} +
+ +
+
+
Web 核心
+
当前桌面 WebView / 浏览器渲染核心
+
+
+
+ {webCoreLabel} +
+ {webCoreDetail && ( +
+ {webCoreDetail} +
+ )} +
+
+ + ); +} + +function SectionConfig() { const [upgradeUrl, setUpgradeUrl] = useState(DEFAULT_UPGRADE_SERVICE_URL); const [upgradeLoading, setUpgradeLoading] = useState(true); const [upgradeSaving, setUpgradeSaving] = useState(false); const [upgradeSaved, setUpgradeSaved] = useState(false); const [upgradeError, setUpgradeError] = useState(''); - const [webCoreInfo, setWebCoreInfo] = useState(null); const [depPython, setDepPython] = useState(true); const [depNode, setDepNode] = useState(true); const [depCsharp, setDepCsharp] = useState(false); @@ -495,18 +553,6 @@ function SectionConfig({ health }) { return () => { cancelled = true; }; }, []); - useEffect(() => { - let cancelled = false; - getCurrentWebCoreInfo() - .then((info) => { - if (!cancelled) setWebCoreInfo(info); - }) - .catch(() => { - if (!cancelled) setWebCoreInfo(null); - }); - return () => { cancelled = true; }; - }, []); - const saveUpgradeUrl = async () => { const baseUrl = upgradeUrl.trim(); if (!baseUrl || !/^https?:\/\//i.test(baseUrl)) { @@ -551,9 +597,6 @@ function SectionConfig({ health }) { { key: 'node', label: 'Node.js 工具', desc: 'pnpm / npm / tsx 等', checked: depNode, toggle: () => setDepNode((v) => !v) }, { key: 'csharp', label: 'C# 工具', desc: 'dotnet SDK / Roslyn 等', checked: depCsharp, toggle: () => setDepCsharp((v) => !v) }, ]; - const programVersionLabel = formatProgramVersion(health?.version); - const webCoreLabel = formatWebCoreLabel(webCoreInfo); - const webCoreDetail = formatWebCoreDetail(webCoreInfo); return ( <> @@ -627,38 +670,6 @@ function SectionConfig({ health }) {
工作空间依赖项

管理 ACECode 安装并提供给 Agent 使用的开发工具

- {/* 程序版本 */} -
-
-
当前版本
-
ACECode 桌面 / TUI / Daemon 同版本号
-
- {programVersionLabel} -
- -
-
-
Web 核心
-
当前桌面 WebView / 浏览器渲染核心
-
-
-
- {webCoreLabel} -
- {webCoreDetail && ( -
- {webCoreDetail} -
- )} -
-
- {/* 依赖项 checkbox 组 */}
ACECode 依赖项
diff --git a/web/src/components/Sidebar.jsx b/web/src/components/Sidebar.jsx index da573aa38..8edc22d32 100644 --- a/web/src/components/Sidebar.jsx +++ b/web/src/components/Sidebar.jsx @@ -48,6 +48,7 @@ import { reconcileSidebarSessions, sessionListNeedsRevealExpansion, sessionMatchesRevealTarget, + sidebarSessionHasWorktree, sidebarRevealTarget, sidebarSessionProjection, upsertSidebarSession, @@ -322,6 +323,7 @@ function SessionRow({ const workspaceHash = s.workspace_hash || s.workspaceHash || ''; const rowKey = pinned ? pinnedSessionKey(workspaceHash, s.id) : ''; const title = sessionDisplayTitle(s, s.name || ''); + const worktreeSession = sidebarSessionHasWorktree(s); const [editing, setEditing] = useState(false); const [draft, setDraft] = useState(title); const committingRef = useRef(false); @@ -480,11 +482,31 @@ function SessionRow({ e.stopPropagation(); onArchive?.(s); }} - className="w-5 h-6 rounded flex items-center justify-center shrink-0 text-fg-mute hover:text-fg hover:bg-surface-hi opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition" + className={clsx( + 'w-5 h-6 rounded flex items-center justify-center shrink-0 text-fg-mute hover:text-fg hover:bg-surface-hi transition', + worktreeSession + ? 'opacity-100' + : 'opacity-0 group-hover:opacity-100 group-focus-within:opacity-100', + )} title="归档" - aria-label="归档" + aria-label={worktreeSession ? '工作树会话,归档' : '归档'} > - + {worktreeSession ? ( + + ) : ( + + )}
); @@ -765,21 +787,21 @@ function WorkspaceGroup({ onClick={(e) => { e.stopPropagation(); onNewSession(ws); }} className="ace-sidebar-workspace-action w-5 h-5 rounded hover:bg-surface-hi flex items-center justify-center shrink-0 transition" title="新增会话" - > + > {hasUnread && } + > {onRemove && ( + > )} {expanded && ( diff --git a/web/src/lib/desktopContextMenu.js b/web/src/lib/desktopContextMenu.js index 541ebd509..c6bdca609 100644 --- a/web/src/lib/desktopContextMenu.js +++ b/web/src/lib/desktopContextMenu.js @@ -32,6 +32,7 @@ export const DESKTOP_CONTEXT_ACTIONS = Object.freeze({ COLLAPSE_DIRECTORY: 'collapse_directory', COPY_PREVIEW_TEXT: 'copy_preview_text', COPY_PREVIEW_METADATA: 'copy_preview_metadata', + COPY_PREVIEW_IMAGE: 'copy_preview_image', COPY_FILE_DIFF: 'copy_file_diff', COPY_ALL_DIFFS: 'copy_all_diffs', LOCATE_IN_FILE_TREE: 'locate_in_file_tree', @@ -230,6 +231,11 @@ export function buildDesktopContextMenuItems({ } if (previewTarget) { + if (previewTarget.kind === 'image' && previewTarget.copyImageUrl) { + addAction(items, DESKTOP_CONTEXT_ACTIONS.COPY_PREVIEW_IMAGE, previewTarget, { + group: GROUPS.CONTENT, + }); + } addAction(items, DESKTOP_CONTEXT_ACTIONS.COPY_PREVIEW_TEXT, previewTarget, { group: GROUPS.CONTENT, enabled: previewTarget.kind === 'text' || previewTarget.kind === 'markdown', @@ -383,6 +389,7 @@ export function previewTargetFromElement(target) { kind: getAttr(el, 'data-desktop-preview-kind', 'desktopPreviewKind') || 'text', size: getAttr(el, 'data-desktop-preview-size', 'desktopPreviewSize'), contentType: getAttr(el, 'data-desktop-preview-content-type', 'desktopPreviewContentType'), + copyImageUrl: getAttr(el, 'data-desktop-preview-copy-image-url', 'desktopPreviewCopyImageUrl'), }; } diff --git a/web/src/lib/desktopContextMenu.test.js b/web/src/lib/desktopContextMenu.test.js index 3b7294e61..d8d0100ac 100644 --- a/web/src/lib/desktopContextMenu.test.js +++ b/web/src/lib/desktopContextMenu.test.js @@ -73,6 +73,31 @@ test('预览选区把引用到聊天放在第一行', () => { assert.equal(items[0].target.path, 'README.md'); }); +test('图片预览显示复制图片动作且非图片预览不显示', () => { + const imageItems = buildDesktopContextMenuItems({ + previewTarget: { + type: 'preview', + path: 'shots/result.png', + kind: 'image', + contentType: 'image/png', + copyImageUrl: 'blob:preview-image', + }, + }); + assert.deepEqual(ids(imageItems), [ + DESKTOP_CONTEXT_ACTIONS.COPY_PREVIEW_IMAGE, + DESKTOP_CONTEXT_ACTIONS.COPY_PREVIEW_TEXT, + DESKTOP_CONTEXT_ACTIONS.COPY_PREVIEW_METADATA, + DESKTOP_CONTEXT_ACTIONS.SELECT_ALL, + ]); + assert.equal(imageItems[0].target.copyImageUrl, 'blob:preview-image'); + assert.equal(imageItems.find((item) => item.id === DESKTOP_CONTEXT_ACTIONS.COPY_PREVIEW_TEXT).enabled, false); + + const pdfItems = buildDesktopContextMenuItems({ + previewTarget: { type: 'preview', path: 'manual.pdf', kind: 'pdf' }, + }); + assert.equal(ids(pdfItems).includes(DESKTOP_CONTEXT_ACTIONS.COPY_PREVIEW_IMAGE), false); +}); + test('文本框显示粘贴和剪切', () => { assert.deepEqual(ids(buildDesktopContextMenuItems({ editable: true })), [ DESKTOP_CONTEXT_ACTIONS.SELECT_ALL, @@ -337,10 +362,19 @@ test('contextTargetsFromElement 提取各类目标', () => { assert.equal(contextTargetsFromElement(file).fileTarget.relativePath, 'src/a.cpp'); const preview = elementFor(PREVIEW_TARGET_SELECTOR, { - 'data-desktop-preview-path': 'README.md', - 'data-desktop-preview-kind': 'markdown', + 'data-desktop-preview-path': 'shots/result.png', + 'data-desktop-preview-kind': 'image', + 'data-desktop-preview-content-type': 'image/png', + 'data-desktop-preview-copy-image-url': 'blob:preview-image', + }); + assert.deepEqual(contextTargetsFromElement(preview).previewTarget, { + type: 'preview', + path: 'shots/result.png', + kind: 'image', + size: '', + contentType: 'image/png', + copyImageUrl: 'blob:preview-image', }); - assert.equal(contextTargetsFromElement(preview).previewTarget.kind, 'markdown'); const review = elementFor(REVIEW_TARGET_SELECTOR, { 'data-desktop-review-kind': 'file', diff --git a/web/src/lib/sidebarSessions.js b/web/src/lib/sidebarSessions.js index 00d850949..191532b8a 100644 --- a/web/src/lib/sidebarSessions.js +++ b/web/src/lib/sidebarSessions.js @@ -48,6 +48,13 @@ function sessionContentChanged(previous, next) { return false; } +export function sidebarSessionHasWorktree(session = {}) { + const worktree = session?.worktree; + if (!worktree || typeof worktree !== 'object') return false; + return [worktree.name, worktree.branch] + .some((value) => String(value || '').trim().length > 0); +} + export function sidebarSessionProjection(sessions = [], expanded = false, limit = SIDEBAR_SESSION_COLLAPSE_LIMIT) { const list = Array.isArray(sessions) ? sessions : []; const max = Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : SIDEBAR_SESSION_COLLAPSE_LIMIT; diff --git a/web/src/lib/sidebarSessions.test.js b/web/src/lib/sidebarSessions.test.js index 089140ca4..8c6bffcdf 100644 --- a/web/src/lib/sidebarSessions.test.js +++ b/web/src/lib/sidebarSessions.test.js @@ -4,6 +4,7 @@ import { reconcileSidebarSessions, sessionListNeedsRevealExpansion, sessionMatchesRevealTarget, + sidebarSessionHasWorktree, sidebarRevealTarget, sidebarSessionProjection, sortSidebarSessionsNewestFirst, @@ -20,6 +21,14 @@ function test(name, fn) { } } +test('sidebarSessionHasWorktree requires a worktree name or branch', () => { + assert.equal(sidebarSessionHasWorktree({}), false); + assert.equal(sidebarSessionHasWorktree({ worktree: {} }), false); + assert.equal(sidebarSessionHasWorktree({ worktree: { name: ' ', branch: '' } }), false); + assert.equal(sidebarSessionHasWorktree({ worktree: { name: 'ses-abc' } }), true); + assert.equal(sidebarSessionHasWorktree({ worktree: { branch: 'worktree-ses-abc' } }), true); +}); + test('five or fewer sidebar sessions are not collapsible', () => { const sessions = Array.from({ length: SIDEBAR_SESSION_COLLAPSE_LIMIT }, (_, i) => ({ id: String(i) })); const result = sidebarSessionProjection(sessions, false); diff --git a/web/src/styles/globals.css b/web/src/styles/globals.css index c7f7f6834..d5604bebc 100644 --- a/web/src/styles/globals.css +++ b/web/src/styles/globals.css @@ -2429,7 +2429,7 @@ body { min-height: 24px; /* 根级文件行没有缩进导轨,.ace-file-leaf-content 的负回拉会把图标 拉到 padding-left 之前;基础内边距必须足够大,否则图标贴面板左边线 */ - padding-left: 16px; + padding-left: 10px; } .ace-file-row:hover { background: var(--ace-surface-hi); } .ace-file-tree-message {