Skip to content

Commit b8bed9e

Browse files
author
weiesky.wangc
committed
chore(release): 1.6.261 — 前端架构整理批次(A1/A3/B1/B6/C真-4/5/6 + Modal mobile 抽屉化 + fix(team-session) unknown 过滤)+ 5-agent code review 采纳 (P1+P2)
详见 history.md。 要点: - A1: CSS !important 净清理 4 处真删 + 3 处类名翻倍特异度方案 + 2 处保留并加 why 注释 - A3: 删 6 张零引用 SVG/PNG (~325KB) - B1: 抽 src/utils/seqResourceLoaders.js (含 errorKind 区分 network/parse) - B6: 抽 isElectron 到 src/env.js - C真-4: 抽 LogTable.jsx + useMemo + 入参防御 - C真-5/6: 抽 imageDownscale.js / presetShortcuts.js - feat(mobile-ui): Plugin/Process/Proxy 三 Modal mobile 改抽屉 + MobileDrawerCloseButton 通用子组件 - fix(team-session): popover 隐藏 name==='unknown' 占位条目 - Review 采纳 P1: 删 apiClient.js (0 caller 死代码) + PresetModal 重复 buildPresetsPayload 收敛 - Review 采纳 P2: 抽 utils/formatters.js (formatSize+formatTimestamp 两处合并) + LogTable useMemo + seqResourceLoaders 边界文档化 + MobileDrawerCloseButton 简化 (CloseOutlined + i18n key ui.closeDrawer)
1 parent 2c3a7b5 commit b8bed9e

28 files changed

Lines changed: 823 additions & 478 deletions

history.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,86 @@
11
# Changelog
22

3+
## 1.6.261 (2026-05-12)
4+
5+
- review(adopt): 5-agent code review 采纳(P1 + P2 全部)
6+
- **P1-1**:删 `src/utils/apiClient.js`(0 caller 死代码,违反 YAGNI;4/5 reviewer 共识)。
7+
- **P1-3**:删 `PresetModal.jsx` 内重复定义的 `buildPresetsPayload`,改 import `src/utils/presetShortcuts.js`(B 类抽取漏改 1 处)。
8+
- **P2-2**:新建 `src/utils/formatters.js`,收敛 `formatSize` + `formatTimestamp` —— LogTable / WorkspaceList 两处重复合并;选用 4 档版本(B/KB/MB/GB,与原 WorkspaceList 等价)。
9+
- **P2-3**:LogTable `selectedLogs` 默认空 Set;preview 数组首元素加 `typeof === 'string'` 守卫,防 server 偶发回 `[null]`
10+
- **P2-4**:LogTable `columns``useMemo` 包,依赖 `[mobile, selectedLogs, onToggleSelect, onOpenLog, onDownloadLog]`,免 antd Table schemaChange 重诊。
11+
- **P2-5**`_seqGuardedFetch` 拆 fetch / r.json() 两段 try,错误带 `errorKind: 'network' | 'parse'`,loadFsSkills `reason` 字段优先用 errorKind 取代 `e.message` —— 区分网络断 vs 后端回非法 JSON。
12+
- **P2-6**`MobileDrawerCloseButton` 内联 X SVG → antd `<CloseOutlined />`;i18n key 从借用的 `ui.closeCachePanel` 改为新增中性 key `ui.closeDrawer`(18 语言全覆盖)。
13+
- **P2-1**`seqResourceLoaders.js` 头注释明确"抽取边界"——静默 lazy-load 走 utils,用户触发 refresh / detail 留 caller inline,是设计选择不是抽取漏。
14+
- **延后到后续 PR (P3)**:三 Modal 专用 CSS 拆分、seqResourceLoaders Hook 化、PluginModal 上传 size 上限、mobile 抽屉 DOM 常驻 → `{open && ...}`、imageDownscale 入参守卫、AbortController、FileContentView 14 处 CodeMirror inline `!important`、lib/plugin-manager SSRF 黑名单等。
15+
16+
- feat(mobile-ui): 插件管理 / CCV 进程管理 / 代理热切换 三个 Modal 在移动端改为左侧滑入抽屉,与文件浏览器 / 日志管理 / 偏好设置等其余菜单项保持一致
17+
- **背景**:截至上一版,左侧菜单中其它项已统一采用 `position:absolute + translateX(-100%)→0 + transition 0.3s + zoom:0.6 inner` 的侧边抽屉模式(`mobileFileExplorerOverlay` / `mobileLogMgmtOverlay` 等),但 PluginModal / ProcessModal / ProxyModal 仍是 Ant Modal 弹窗,视觉与交互双重不一致。
18+
- **改造(src/components/{PluginModal,ProcessModal,ProxyModal,MobileDrawerCloseButton}.jsx + src/App.module.css + src/components/AppHeader.module.css)**
19+
- `App.module.css` 抽出 `.mobileDrawerOverlay / .mobileDrawerOverlayVisible / .mobileDrawerInner` 三个**公共基类**(替代之前的三套重复 mobile{Plugin,Process,Proxy}Overlay/Inner),含 `:global(html.mobile-ios) {scale(0.6)}``:global(html.pad-mode) {zoom:1}` 变体;overlay 加 `will-change: transform` 提示浏览器为动画建独立合成层、缓解 iOS Safari 在 inner 含 scale 时的滑动卡顿。零新增 `!important`
20+
- `AppHeader.module.css` 追加 `.pluginModalScroll / .pluginModalFooterBar / .processModalScroll / .processModalFooterBar / .proxyModalScroll` 五个内层类,提供抽屉 scroll+footerBar 的 flex 布局;footerBar 显式 `flex-shrink: 0` 防止在 zoom 容器中被压扁。抽屉 header / title / close 按钮直接复用 App.module.css 中既有 `.mobileLogMgmtHeader/Title/Close`(mobileSettingsOverlay/mobilePromptOverlay 已先例),不再重复定义。
21+
- 新增 `src/components/MobileDrawerCloseButton.jsx`:把三处一字不差的 `<button>` + 内联 X 图标 SVG 抽成通用子组件,三个 Modal 通过 `<MobileDrawerCloseButton onClose={onClose} />` 引用。
22+
- 三个 Modal 各加 `if (isMobile)` 提前 `return` 分支:移动端渲染 overlay→header→inner→scroll[+footerBar] 四层 DOM;桌面端 `<Modal>` 全部字符级保留,无视觉/行为回归。
23+
- 嵌套二级 Modal(删插件 / kill 进程 / 删代理 / CDN 安装)继续保持 Ant Modal 形态,通过默认 portal-to-body 逃出抽屉 inner 的 `transform: scale` 堆叠上下文,z-index 1100 高于抽屉 z-index 100,自然叠在最上层。
24+
- Mobile.jsx 无需修改:`_closeAllMobileOverlays()` 早已把 `pluginModalVisible / processModalVisible / proxyModalVisible` 三个旗标并入互斥逻辑(`Mobile.jsx:91-93`),三处挂载点 `:1004-1019` API 不变。
25+
- 关闭按钮 aria-label 复用现有 `ui.closeCachePanel` i18n key,不新增翻译条目。
26+
- **2 轮 5-agent UltraReview 采纳点**:① CSS 抽公共基类去除三套重复(架构 P0 + 质量 P0 跨轮共识);② 抽 `MobileDrawerCloseButton` 子组件去除 SVG 重复(质量 P0);③ overlay 加 `will-change: transform` 缓解 iOS 滑动卡顿(性能 P2);④ ProcessModal 变量命名 `refreshBtn``refreshBtnNode` 与同文件 `titleNode/tableNode` 后缀一致(质量 P1)。其余 P1/P2(fetch AbortController、isMobile reactive、遮罩点击关闭、apiKey masking 等)经核查为非本次引入 / 与现有项目约定冲突 / 经第一轮已核为误判,未采纳。
27+
28+
- fix(team-session): Team 会话 popover 隐藏 name="unknown" 的无名条目
29+
- **症状**:sidebar 左侧人形图标弹出的「Team 会话 (N)」列表里,偶尔出现 name 为 "unknown"、metadata 全 0(`0p · 0t`)的条目,对用户无任何信息价值。
30+
- **根因**`src/utils/teamSessionParser.js:80` 在 TeamCreate tool_use 缺少 `team_name`/`teamName` 字段时兜底 `teamName = 'unknown'``:98` cross-file TeamDelete(TeamCreate 在前一个 JSONL)反推不出名字时同样兜底 'unknown'。parser conservative 保留底层数据用于调试/历史追溯,但 popover UI 不应当展示这些占位项。
31+
- **修复(src/components/TeamSessionPanel.jsx:79,仅 1 文件、1 行)**`useMemo` 内追加 `.filter(t => t.name && t.name !== 'unknown')`。下游所有引用(标题计数 / map 渲染 / classifyTeams / useEffect 触发 / `/api/team-status` 查询)自动级联到 filtered 列表,无需多处同步——全部 team 都是 unknown 的会话整个按钮自动隐藏(line 149 `if (teamSessions.length === 0) return null;` 自动级联);副作用:省掉对 unknown teams 的 runtime status 查询,与 UI 隐藏一致。parser 不动。
32+
- **测试**`npm run build` 通过;`npm run test` 1891/1891 全绿。
33+
34+
- style(css): 清理 4 处 `!important`(违反 CLAUDE.md 全局禁令)+ 2 处保留并加注释说明物理无法绕过
35+
- **背景**:CLAUDE.md 明令 "Using `!important` in CSS is prohibited",但 src/ 实际散落 7 处。7-agent 调研(含挑战派/隐藏风险/fact-checker)核实每条的特异度博弈后分两批处理。
36+
- **真删的 4 处**
37+
- `src/global.css:495` `.ant-tabs .ant-tabs-nav::before` 已有 (0,0,2,1) 特异度,原 !important 多余。
38+
- `src/components/ChatView.module.css:386` `.lastResponseDivider::before/::after` 用类名翻倍 `.lastResponseDivider.lastResponseDivider::before` 达 (0,0,2,1),超过 AntD Divider 的 `:where()` 注入特异度。
39+
- `src/components/TeamSessionPanel.module.css:230` 改选择器为 `.teamAgentCard.teamAgentCardActive` (0,0,2,0),超过 `.teamAgentCard:hover` (0,0,1,1) — 解决"选中态被 hover 压过"的本质特异度冲突。
40+
- `src/components/TerminalPanel.module.css:55` `.terminalContainer :global(.xterm-helper-textarea)` (0,0,2,0) 已超过 xterm.css 单类 (0,0,1,0),去 !important(注释提示若 xterm.js inline 注入 caret-color 则需 revert)。
41+
- **保留并加注释的 2 处(CSS 物理无法去)**
42+
- `src/global.css:404-409` 全局 `*:focus { outline/box-shadow: none }`:要超过 AntD v5 `:not(:disabled):focus-visible` (0,0,3,0) 需 3 类以上,需污染 JSX 添加 hook 类——不值得;注释明确标"intentional exception"。
43+
- `src/components/ChatMessage.module.css:238-245` 流式 thinking 折叠 transition:rc-motion 通过 inline style 注入 `transition`,inline style (1,0,0,0) 高于任何 CSS 规则,**只有 !important 能破** —— 非特异度问题,CSS 无解;注释明确这是项目级规则的明确例外。
44+
- **取舍记录**:另两种思路被否决——(a) `@layer` cascade:AntD v5.29 CSS-in-JS 注入时机不明,不可靠;(b) 删 `*:focus` 整条规则:影响 a11y/视觉太广,回归风险高。**视觉验证待办**:4 处真删点(AntD Tabs 底边线 / Last Response 虚线 / TeamAgent 选中态 / xterm 光标)需用户实机目视确认。
45+
- **测试**`npm run build` 通过。
46+
47+
- chore(assets): 删除 6 张完全未引用的图片(节省 ~325KB)
48+
- **背景**:调研 agent 报告 src/img/ccv-logo.png + claude/{entrance,tickle,exit,waiting,thinking}.svg 在 src/ 全局零引用。fact-check 跨 codebase(含 server.js / lib/ / cli.js / electron/ / README)确认无任何路径硬编码或动态拼接引用;test/svg-sanitize.test.js 只引用配置常量不加载具体文件,删除无影响。
49+
- **保留**:claude/{orbiting,shimmer,writing}.svg 是 src/components/ChatView.jsx 和 src/utils/helpers.js 实际引用的动画素材,不动。
50+
- **可回滚**:git 历史可恢复。
51+
52+
- refactor(loaders): 抽 `src/utils/seqResourceLoaders.js` 取代 AppHeader/Mobile 6 处重复的 seq-guarded 三态加载
53+
- **重复代码**:AppHeader.jsx 与 Mobile.jsx 各自实现 `reloadFsSkills`/`loadMemory`/`loadClaudeMdList` 三个 lazy-load 方法,3 × 2 = 6 份代码近乎逐行相同(seq 计数防 workspace 切换脏回包污染 + 三态契约 null/false/data + 失败回退策略)。原代码注释已自陈"短期接受重复,TODO 后续抽 src/utils/cacheFetch.js"。
54+
- **抽象形态**:3 个 dedicated loader 而非 1 个万能函数 —— 因 fact-checker 核实 3 个 loader 实际有契约差异:`loadFsSkills` 返回 `{ok, skills, reason}` 给 caller 决策且失败时保留前次成功数组(避免 popover chip 从乐观态回退);`loadProjectMemory` / `loadClaudeMdList` 无返回值,失败直接 `false`。class 组件不能用 hooks,故 utility 接受 `this` 作为 component 参数直接操作 seq 字段和 setState。私有 `_seqGuardedFetch` 封装"seq++ → fetch → json → stale 检查"原语。
55+
- **影响**:AppHeader 三个方法各从 ~20 行 → 1 行 delegate;Mobile 同样压缩。原`isLocalLog` 来源差异(AppHeader 用 `props.isLocalLog`、Mobile 用 `this._isLocalLog`)通过 options 参数注入,保持各自语义。
56+
- **不抽**`handleRefreshMemory`(带 toast 的主动刷新变体,与 lazy-load 的 silent 策略不同)和 `loadMemoryDetail`/`loadClaudeMdDetail`(payload 形状不同)保留原样,避免过度抽象损失语义。
57+
- **测试**`npm run build` 通过;`npm run test` 1891/1891 全绿。
58+
59+
- refactor(api): 新增 `src/utils/apiClient.js`(helper-only,不迁移现有 fetch 调用)
60+
- **决策记录**:调研发现 src/ 下有 95 处 fetch 调用,形态异质(31 处 await 风格、2 处 .then 风格、错误处理散落 silent catch / message toast / 自定义状态 / fire-and-forget)。若强制全量迁移到 wrapper,会损失各处自定义错误语义且 churn 高(95 site);若不建 helper,未来新代码继续重复样板。**折中**:建轻 helper 放着,老代码不动,新代码自然采用。
61+
- **API**`apiCall(path, init)` 返回 `{ ok, status, data, error? }` 形状(不抛错,与现有 .ok 检查风格兼容);`apiGet/apiPost/apiPut/apiDelete` 是常用方法 sugar。
62+
- **测试**:未引入运行时行为变化(无 caller);build 通过。
63+
64+
- refactor(env): 新增 `isElectron``src/env.js`,迁移 1 处直检
65+
- **背景**:env.js 已集中 `isMobile`/`isPad`/`isIOS`,但 Electron 检测散在 2 处直接 `window.electronAPI`。仅 1 处是纯存在检查(App.jsx:523 更新提示 UI),其他 1 处(WorkspaceList.jsx:214 `?.launchWorkspace`)同时检方法存在,精度更高保留不迁移。
66+
- **实现**`export const isElectron = typeof window !== 'undefined' && !!window.electronAPI;` 在模块加载时求值(Electron preload 在页面加载前注入 `window.electronAPI`,时序安全)。
67+
- **测试**:build + 1891 tests 全绿。
68+
69+
- refactor(logtable): 抽 `AppBase.renderLogTable` (~110 行) 为 `src/components/LogTable.jsx` 函数组件
70+
- **背景**:AppBase.jsx 是 PC App 和 Mobile 共享的 2011 行基类。renderLogTable 是其中 110 行纯渲染块,含 6 列 Table 列定义 + 关联回调,与 AppBase 状态机无强耦合。`formatTimestamp``formatSize` 两个 helper 在 src/ 全局只在此一处调用,一并迁入 LogTable.jsx 内部。
71+
- **影响**:AppBase 删除 6 个无关 antd imports(Table/Checkbox/Popover/Tag/DownloadOutlined);renderLogTable 从 110 行变 11 行 delegate;删除 formatTimestamp/formatSize 两个 instance 方法(共 11 行)。AppBase 净减 ~120 行。
72+
- **接口**:LogTable 接收 6 个 props(logs/mobile/selectedLogs/onToggleSelect/onOpenLog/onDownloadLog)。class field 箭头函数引用稳定,无 React batching 风险。
73+
- **审计**:3-agent code review(功能等价 / 代码质量 / 测试依赖)未发现 P0/P1 阻塞;Reviewer 提的 propTypes 建议未采纳——项目全局 0 propTypes 使用,是约定。
74+
- **测试**:build + 1891 tests 全绿。
75+
76+
- refactor(utils): 抽 `TerminalPanel._downscaleForRetina` (~26 行) 为 `src/utils/imageDownscale.js`
77+
- **判定**:fact-checker 核实该方法是纯通用 Retina 图片压缩(dpr > 1 → canvas 重绘到 1x),无 PTY / Terminal / xterm 特定逻辑。导出 `downscaleForRetina(file)` 函数。Graceful fallback(canvas 不可用 / blob null / img.onerror 时 resolve(file))保留原行为,注释解释"intentional silent fallback for upload robustness"。
78+
- **测试**:build + 1891 tests 全绿。
79+
80+
- refactor(utils): 抽 `TerminalPanel._savePresetShortcuts` payload 序列化为 `src/utils/presetShortcuts.js`
81+
- **背景**:原方法 13 行混合"payload 构造 + props 回调"。payload 部分(items.map → presetShortcuts、dismissed Set spread → dismissedBuiltinPresets)是纯数据变换,与组件 state 解耦后变 14 行 pure function `buildPresetShortcutsPayload(items, dismissed)`。组件方法保留 props 调用部分,从 13 行 → 3 行。
82+
- **测试**:build + 1891 tests 全绿。
83+
384
## 1.6.260 (2026-05-11)
485

586
- fix(image-viewer): Mac 触控板 pinch 缩放灵敏度过高根治 + React passive wheel listener 修复 + 5-agent UltraReview 采纳

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cc-viewer",
3-
"version": "1.6.260",
3+
"version": "1.6.261",
44
"description": "Claude Code Logger visualization management tool",
55
"license": "MIT",
66
"main": "server.js",

src/App.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22
import { ConfigProvider, Layout, theme, Modal, Button, Checkbox, Spin, Alert, message, Tooltip } from 'antd';
33
import { UploadOutlined, DeleteOutlined, ReloadOutlined } from '@ant-design/icons';
44
import AppBase, { styles } from './AppBase';
5-
import { isMobile, setViewMode } from './env';
5+
import { isMobile, isElectron, setViewMode } from './env';
66
import { uploadFileAndGetPath } from './components/TerminalPanel';
77
import AppHeader from './components/AppHeader';
88
import RequestList from './components/RequestList';
@@ -520,7 +520,7 @@ class App extends AppBase {
520520
{this.state.updateInfo && <p><strong>{t('ui.update.latest')}:</strong> v{this.state.updateInfo.version}</p>}
521521
<p style={{ marginTop: 12 }}><strong>{t('ui.update.npm')}</strong></p>
522522
<code style={{ display: 'block', background: 'var(--bg-code)', padding: '8px 12px', borderRadius: 6, fontSize: 13 }}>npm update -g cc-viewer</code>
523-
{typeof window !== 'undefined' && window.electronAPI && (<>
523+
{isElectron && (<>
524524
<p style={{ marginTop: 16 }}><strong>{t('ui.update.electron')}</strong></p>
525525
<p style={{ color: 'var(--text-tertiary)', fontSize: 13 }}>{t('ui.update.electronDesc')}</p>
526526
<ol style={{ color: 'var(--text-tertiary)', fontSize: 13, paddingLeft: 20, margin: '6px 0' }}>

src/App.module.css

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,3 +1026,51 @@
10261026
color: var(--text-primary);
10271027
margin: 0;
10281028
}
1029+
1030+
/* ===== Mobile 侧边抽屉公共基类 =====
1031+
* 当前由插件管理 / CCV 进程管理 / 代理热切换三处共用;模板与既有
1032+
* mobileFileExplorerOverlay / mobileLogMgmtOverlay 一致:
1033+
* - overlay 自身负责 translateX 动画与 z-index 100;will-change 提示浏览器为
1034+
* transform 单独建合成层,缓解 iOS Safari 在 inner 含 scale 时滑动卡顿。
1035+
* - inner 在内部用 zoom:0.6 还原桌面内容密度;iOS Safari 改用 scale(0.6);pad-mode 复位 zoom:1。
1036+
*/
1037+
1038+
.mobileDrawerOverlay {
1039+
position: absolute;
1040+
inset: 0;
1041+
transform: translateX(-100%);
1042+
transition: transform 0.3s ease;
1043+
will-change: transform;
1044+
z-index: 100;
1045+
background: var(--bg-base-alt);
1046+
display: flex;
1047+
flex-direction: column;
1048+
overflow: hidden;
1049+
}
1050+
1051+
.mobileDrawerOverlayVisible {
1052+
transform: translateX(0);
1053+
}
1054+
1055+
.mobileDrawerInner {
1056+
flex: 1;
1057+
min-height: 0;
1058+
display: flex;
1059+
flex-direction: column;
1060+
zoom: 0.6;
1061+
}
1062+
1063+
:global(html.mobile-ios) .mobileDrawerInner {
1064+
zoom: 1;
1065+
position: absolute;
1066+
top: 0;
1067+
left: 0;
1068+
width: calc(100% / 0.6);
1069+
height: calc(100% / 0.6);
1070+
transform: scale(0.6);
1071+
transform-origin: top left;
1072+
}
1073+
1074+
:global(html.pad-mode) .mobileDrawerInner {
1075+
zoom: 1;
1076+
}

0 commit comments

Comments
 (0)