fix(desktop): 统一选择器 classic rail 超高时内部滚动,不再叠压 footer (#3516) - #3520
fix(desktop): 统一选择器 classic rail 超高时内部滚动,不再叠压 footer (#3516)#3520FIERsity wants to merge 2 commits into
Conversation
|
| Filename | Overview |
|---|---|
| apps/desktop/src/renderer/components/new-chat/UnifiedModelRail.tsx | 新增的滚动约束与实际父级 flex 高度链匹配,滚动条提示 effect 也由已记忆化的 items 控制,未发现可达缺陷。 |
| apps/desktop/src/renderer/tests/unifiedModelPanelRendering.test.tsx | 新测试覆盖 rail 必需样式、超量来源的完整顺序、唯一分隔线及可访问性属性,未发现错误断言。 |
Reviews (1): Last reviewed commit: "fix(desktop): 统一选择器 classic rail 超高时内部滚动..." | Re-trigger Greptile
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a5ac684e6d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
自定义来源一多,rail 的 34px 格位累计高度超过面板可用高度:根节点没有 min-h-0 / overflow-y-auto 约束,子项按 visible 溢出向下绘制,直接叠到底部 「添加模型」footer 上并越出弹层(makecindy#3516)。 按 issue 分析建议收口: - 根节点补 min-h-0 + overflow-y-auto + overscroll-contain,w-12 shrink-0 骨架不变 —— 与右侧列表(UnifiedModelPanel 的同一套收缩滚动链)对齐; - 挂 data-unified-model-rail 定位标记,items 变化后对 rail 元素调用现有 flashScrollbar(真可滚才闪),提示左栏下方还有来源; - 不用 scrollbar-gutter:stable(48px 窄栏要留给 34px 按钮)。 测试:unifiedModelPanelRendering 增补两条 rail 接线锁 —— 骨架类保留; 24 个来源格全部渲染、data-rail-item 顺序与可达性文案不丢。 Signed-off-by: FIERsity <FIERsity@users.noreply.github.com>
a5ac684 to
303e85c
Compare
…3516) review P2(chatgpt-codex-connector):overflow-y-auto 让 rail 成为滚动容器后, Windows 等非 overlay scrollbar 环境的全局 12px ::-webkit-scrollbar 槽位会扣除 布局宽度 —— 48px 栏减 12px 槽、12px 侧距与 1px 边框只剩 ~23px,34px 格位放不下。 处理:复用既有 .scrollbar-hide 工具类(F-FI-3,globals.css)隐藏原生槽, 滚轮 / 触控板 / 键盘照常内部滚动;折叠侧栏窄 rail(CCAgentSidebarUpper) 是同一取舍的先例。上一提交引入的 flashScrollbar 提示随之失去视觉载体, 一并摘除(effect + ref + import),data-unified-model-rail 定位标记保留。 测试:骨架断言补 scrollbar-hide;101 passed,typecheck 通过。 Signed-off-by: FIERsity <FIERsity@users.noreply.github.com>
MagicLizi
left a comment
There was a problem hiding this comment.
格式门未通过,请补全后再推。
- Description 缺必填段落「风险」。fix 类 PR 需要写清这次改动的风险与回滚/影响面。
补上「风险」段落后重新请求审查即可。
|
命中 UI 路径( 这条是提醒,不单独阻断合并。 |
|
补一下界面效果证据(按源码尺寸复刻的自包含演示页:48px rail / 34px 格位圆角 9 / 1px hairline / footer「添加模型」,面板高钳 300px、放入 ★ + 全部 + 8 个自定义来源):
说明:这是按 main 源码同构复刻的演示页截图(非实机截图);真实运行态即 自包含演示页 unified-rail-demo.html<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>#3520 统一选择器 rail 溢出修复 · 界面对比</title>
<style>
/* ── 近似 Cindy 浅色主题 token(仅用于演示还原)────────────────────── */
:root {
--page-bg: #efece5;
--panel-bg: #fbfaf6;
--panel-border: #dedbd2;
--hairline: #e6e3da;
--text: #2d2b27;
--text-tertiary: #a39f94;
--badge-bg: #f1efe8;
--badge-fg: #55524a;
--hover-row: #eeece4;
--active-chip: #33312a;
--chip-fg: #faf9f4;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, "PingFang SC", "Segoe UI", sans-serif;
background: var(--page-bg);
color: var(--text);
padding: 28px 30px;
font-size: 13px;
}
h1 { font-size: 15px; margin-bottom: 4px; }
.sub { color: var(--text-tertiary); font-size: 12px; margin-bottom: 22px; }
.row { display: flex; gap: 26px; align-items: flex-start; }
.case { width: 380px; }
.tag {
display: block; font-size: 12px; margin-bottom: 8px; line-height: 1.45;
color: var(--text); font-weight: 600;
}
.tag small { color: var(--text-tertiary); font-weight: 400; }
/* ── 统一面板骨架(ModelSelector.tsx:2756 / UnifiedModelPanel / UnifiedModelRail 同构)
面板高度故意钳到 300px,来源格位共 9 个(合计 >300px 触发超高)────────── */
.panel {
height: 300px;
background: var(--panel-bg);
border: 1px solid var(--panel-border);
border-radius: 12px;
display: flex;
flex-direction: column;
min-h: 0;
overflow: hidden;
}
.search {
flex-shrink: 0;
display: flex; align-items: center; gap: 8px;
padding: 11px 14px;
border-bottom: 1px solid var(--hairline);
color: var(--text-tertiary);
}
.mid { display: flex; min-height: 0; min-width: 0; flex: 1; }
.list-col { position: relative; display: flex; min-height: 0; min-width: 0; flex: 1; flex-direction: column; }
.models { flex: 1; min-height: 0; overflow-y: auto; padding: 8px 8px 10px; }
.group-label { padding: 8px 8px 4px; color: var(--text-tertiary); font-size: 12px; }
.mrow {
display: flex; align-items: center; justify-content: space-between; gap: 8px;
padding: 8px 10px; border-radius: 8px;
}
.mrow:hover, .mrow.sel { background: var(--hover-row); }
.mrow .name { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.eff { color: var(--text-tertiary); font-size: 12px; flex-shrink: 0; }
.dotmark {
width: 22px; height: 22px; border-radius: 50%; flex-shrink: 0;
background: var(--badge-bg); color: var(--badge-fg);
display: inline-flex; align-items: center; justify-content: center;
font-weight: 700; font-size: 12px; margin-right: 8px;
}
.footer {
flex-shrink: 0;
display: flex; align-items: center; justify-content: space-between; gap: 8px;
padding: 9px 14px;
border-top: 1px solid var(--hairline);
color: var(--text-tertiary); font-size: 12.5px;
}
.footer .add { display: flex; align-items: center; gap: 6px; }
/* ── rail(48px;34px 格位;格间 2px;侧距 6px;右缘 1px 分隔线)────────── */
.rail {
width: 48px;
flex-shrink: 0;
display: flex; flex-direction: column; align-items: center; gap: 2px;
border-right: 1px solid var(--hairline);
padding: 8px 6px;
}
.rail-btn {
width: 34px; height: 34px; border-radius: 9px; flex-shrink: 0;
border: none; background: none; cursor: pointer;
display: flex; align-items: center; justify-content: center;
color: var(--text-tertiary);
}
.rail-btn:hover { background: var(--hover-row); }
.rail-btn.active { background: var(--active-chip); color: var(--chip-fg); }
.rail-sep { width: 22px; border-top: 1px solid var(--hairline); margin: 3px 0; flex-shrink: 0; }
/* ── 三种变体 ──────────────────────────────────────────── */
/* A 修复前:根节点无 min-h-0 / overflow —— 子项按 visible 向下绘制,叠压 footer(#3516) */
.before-fix .rail { min-height: auto; overflow: visible; }
/* B 修复后:min-h-0 + overflow-y-auto + overscroll-contain + scrollbar-hide(PR 提交态) */
.after-fix .rail {
min-height: 0; overflow-y: auto; overscroll-behavior-y: contain;
-ms-overflow-style: none; scrollbar-width: none;
}
.after-fix .rail::-webkit-scrollbar { display: none; }
/* C 反例:同样式但保留原生滚动条槽 —— Windows 非 overlay 下 12px 槽挤掉格位(review P2) */
.native-gutter .rail {
min-height: 0; overflow-y: auto; overscroll-behavior-y: contain;
}
/* 显式样式化 ::-webkit-scrollbar 会把 Chromium 从 overlay 切到占布局的经典模式,
等效于 Windows 非 overlay 环境:48px 内容区被扣掉 12px。 */
.native-gutter .rail::-webkit-scrollbar {
width: 12px;
}
.native-gutter .rail::-webkit-scrollbar-track {
background: repeating-linear-gradient(45deg, #ddd9ce 0 4px, #efece3 4px 8px);
}
.native-gutter .rail::-webkit-scrollbar-thumb {
background: #8f8c82; border-radius: 6px;
border: 2px solid transparent; background-clip: padding-box;
}
/* 标出被挤压的 34px 格位(仅反例用,红虚线便于一眼看出溢出方向) */
.native-gutter .rail-btn { outline: 1px dashed rgba(178, 74, 58, 0.55); outline-offset: -1px; }
svg { display: block; }
</style>
</head>
<body>
<h1>统一模型选择器 · 左侧 rail 超高处理(#3520,对应 issue #3516)</h1>
<div class="sub">面板骨架与类名按 main 源码复刻(48px rail / 34px 格位圆角 9 / 1px hairline / footer「添加模型」)。面板高度固定 300px,放入 ★ + 全部 + 8 个自定义来源,格位总高必然超高。</div>
<div class="row">
<!-- A -->
<div class="case before-fix">
<span class="tag">A · 修复前(#3516)<small> · rail 无纵向约束 → 格位溢出叠压「添加模型」</small></span>
<div class="panel">
<div class="search">🔍<span style="flex:1">搜索模型…</span></div>
<div class="mid">
<div class="rail" id="rail-a"></div>
<div class="list-col"><div class="models" id="list"></div></div>
</div>
<div class="footer"><span class="add">➕ 添加模型</span><span>尝试样式B 切回老版</span></div>
</div>
</div>
<!-- B -->
<div class="case after-fix">
<span class="tag">B · 修复后(PR 提交态)<small> · min-h-0 + overflow-y-auto + scrollbar-hide → 栏内滚动,footer 干净</small></span>
<div class="panel">
<div class="search">🔍<span style="flex:1">搜索模型…</span></div>
<div class="mid">
<div class="rail" id="rail-b"></div>
<div class="list-col"><div class="models" id="list-b"></div></div>
</div>
<div class="footer"><span class="add">➕ 添加模型</span><span>尝试样式B 切回老版</span></div>
</div>
</div>
<!-- C -->
<div class="case native-gutter">
<span class="tag">C · 反例(P2,已规避)<small> · 若不藏原生槽,Windows 12px 滚动条把 34px 格位挤变形</small></span>
<div class="panel">
<div class="search">🔍<span style="flex:1">搜索模型…</span></div>
<div class="mid">
<div class="rail" id="rail-c"></div>
<div class="list-col"><div class="models" id="list-c"></div></div>
</div>
<div class="footer"><span class="add">➕ 添加模型</span><span>尝试样式B 切回老版</span></div>
</div>
</div>
</div>
<script>
// 屏幕阅读器兼容写法与源码一致:title/aria-label/data-rail-item。
const providers = ["qwen", "grok", "glm", "deepseek", "kimi", "mistral", "llama", "flux"];
const letter = (id) => id[0].toUpperCase();
const RAIL_ALL_SVG = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>';
const STAR_SVG = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26"/></svg>';
function railHTML() {
let out = `<button class="rail-btn" title="收藏" aria-label="收藏">${STAR_SVG}</button>`;
out += `<div class="rail-sep" aria-hidden="true"></div>`;
out += `<button class="rail-btn active" title="全部" aria-label="全部">${RAIL_ALL_SVG}</button>`;
for (const p of providers) {
out += `<button class="rail-btn" title="${p}" aria-label="${p}" data-rail-item="provider:${p}"><span class="dotmark" style="margin-right:0">${letter(p)}</span></button>`;
}
return out;
}
function modelsHTML(withGroup) {
let out = "";
if (withGroup) out += `<div class="group-label">内置目录</div>`;
out += `<div class="mrow"><span class="dotmark">G</span><span class="name">GLM-5.3</span><span class="eff">▙ 高</span></div>`;
out += `<div class="mrow sel"><span class="dotmark">G</span><span class="name">GLM-5.3-Flash</span><span class="eff">▙ 高</span></div>`;
return out;
}
document.getElementById("rail-a").innerHTML = railHTML();
document.getElementById("rail-b").innerHTML = railHTML();
document.getElementById("rail-c").innerHTML = railHTML();
document.getElementById("list").innerHTML = modelsHTML(true);
document.getElementById("list-b").innerHTML = modelsHTML(true);
document.getElementById("list-c").innerHTML = modelsHTML(true);
// 让 B/C 与真实状态一致:默认停在「全部」视图。
</script>
</body>
</html> |


Closes #3516
这次改了什么
摘要
统一模型选择器 classic(样式 A)左侧 rail 原先没有纵向约束:自定义来源一多,34px 格位累计高度超过面板可用高度时,根节点既不收缩也不裁剪(默认
overflow: visible),溢出格位向下绘制,直接叠压底部「添加模型」footer 并越出弹层(#3516)。本 PR 给 rail 补上与右侧列表同一套收缩滚动链,超高时在自己内部滚动,footer 不再被遮挡。变更类型
fix缺陷修复范围
UnifiedModelRail根节点补min-h-0 overflow-y-auto overscroll-contain;w-12 shrink-0骨架不变;scrollbar-hide(既有.scrollbar-hide工具类,globals.css F-FI-3):Windows 等非 overlay scrollbar 环境的全局 12px 槽位会把 48px 栏挤到放不下 34px 格位(review P2),原生槽必须藏、滚轮照常滚 —— 与折叠侧栏窄 rail(CCAgentSidebarUpper)同一取舍;data-unified-model-rail定位标记,供测试定位;scrollbar-gutter: stable;滚动条视觉提示(flashScrollbar在隐藏原生槽后无视觉载体,已从初版方案中摘除);badge(样式 B)与 original 选择器不受影响(badge 本就不渲染 rail)。UI 变化
平台:Desktop(macOS 实测,Electron renderer 层,CSS class 级改动跨三平台一致)。
界面效果证据(对比演示图,亦可从 pr-assets 分支 取 png 与自包含 HTML;修复前叠压形态见 #3516 原 issue 配图):
A 修复前:rail 格位溢出绘制、叠压「添加模型」footer;
B 修复后(本 PR 提交态):栏内滚动,footer 干净;
C 反例(review P2 已规避):不藏原生滚动条槽时 Windows 12px 槽对 34px 格位的挤压。
引用的设计规范:
--model-item-hover)、圆角与宽度契约;怎么验证的
自动验证
手工验证
Linux/Windows 矮窗口 + 15+ 来源的实机矩阵(Light/Dark、滚轮/触控板在 rail 与列表间的滚动归属、Tab 焦点进入视口、badge/original 回归)随 CI 单测与本仓库实机 review 一并确认。
未执行的验证
未在本机逐一走查 Windows 形态(占宽问题经 review P2 指认后已按工具类先例消除,如 review 需要实机录屏再跟进)。