feat(desktop): 分支下拉加搜索框,支持按分支名过滤 - #3844
Conversation
分支多的时候,分支 chip 点开是个纯列表,只能逐条肉眼找。这里在下拉顶部加了 一个搜索输入框:打开即聚焦,按分支名做大小写不敏感的子串过滤,Enter 选第一个 匹配项,↑↓ 在列表内移动,关闭时清空搜索词。 下拉容器从 Radix DropdownMenu 换成 Popover。菜单自己会把单字符按键拿去做 菜单项跳转(react-menu 的 Content onKeyDown → handleTypeaheadSearch),Item 的 onPointerMove 还会把焦点抢到鼠标掠过的那一项 —— 两条都跟"面板里有个输入框" 冲突,后者靠 stopPropagation 也绕不掉。仓内另两处带搜索的下拉 (OneshotModelPinPicker / ScriptCapabilityMultiSelect)同样是 Popover。 只动 renderer:main 侧 listBranches 已经返回全量本地分支,过滤放在渲染层做, IPC 契约与 device-link 隧道零改动。远程分支(origin/*)不在本次范围。 design-inventory 台账随 radii 计数变化重新生成。 Signed-off-by: luojiajun <luo.jj@trip.com>
|
| Filename | Overview |
|---|---|
| apps/desktop/src/renderer/components/new-chat/WorktreeChipsRow.tsx | 分支下拉已改为可搜索 Popover,核心过滤和选择流程清晰,但自定义 listbox 没有采用仓内既有的 roving tabIndex 与完整键盘导航模式。 |
| apps/desktop/src/renderer/i18n/locales/zh-CN/common.json | 新增搜索占位符与无匹配结果文案,并已在五种语言中保持键一致。 |
| docs/design-rules/design-inventory.md | 生成台账更新了快照日期,并将相关界面的裸圆角计数增加一项。 |
Prompt To Fix All With AI
### Issue 1
apps/desktop/src/renderer/components/new-chat/WorktreeChipsRow.tsx:617-632
**列表框焦点管理不完整**
每个 `role="option"` 按钮都会进入默认 Tab 顺序,而且列表只处理上下方向键;分支较多时,键盘用户需要逐项 Tab 穿过列表,Home/End 也无法跳到首尾。请沿用仓内其他选择器的 roving `tabIndex` 与完整列表导航模式。
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "feat(desktop): 分支下拉加搜索框,支持按分支名过滤" | Re-trigger Greptile
自动 review 指出:每个 role="option" 都在默认 Tab 序里,分支上百条时键盘 用户要逐项 Tab 穿过整个列表 —— 而"分支很多"恰好就是这个搜索框要解决的场景。 改成仓内既有的 roving tabIndex 形态(见 HookConnectionsSection 的单选组): 首项 tabIndex=0、其余 -1,Tab 一次进列表、再 Tab 就走出去,组内换项靠 ↑↓ 与 Home/End。Home/End 只挂在列表容器上,搜索框里这两个键留给文本光标。 review 原话说"沿用仓内其他选择器的 roving tabIndex 模式",但实际对齐的那个 先例(OneshotModelPinPicker)本身没有 tabIndex 管理;这里按 HookConnectionsSection 的写法补,不回头改那个先例。 Signed-off-by: luojiajun <luo.jj@trip.com>
|
已按 Greptile review 补上 一点更正:review 说「沿用仓内其他选择器的 roving tabIndex 模式」,但本 PR 原本对齐的那个先例 Tab 穿越本身确实是真问题,而且「分支很多」恰好就是这个搜索框要解决的场景,所以这条采纳了。 补充验证: 实机双模式目检仍未做,原因同 PR 描述「未执行的验证」段,未变。 |
|
@wpr317694-del 这条 PR 改了用户看得见的分支选择交互(下拉加搜索框),需要维护者确认后再继续合并流程。 请维护者直接在本 PR 上 Approve;如果要改,请 Request Changes。讨论 issue:#3845 |
单测意外抓到真 bug:pick() 里直接 setOpen(false),而受控 Popover 的 onOpenChange 只在 Radix 自己发起关闭时才触发 —— 程序化关闭不走那条回调,于是 "关闭即清空搜索词"被绕过。表现:搜 feishu、点中结果、再点开下拉,搜索框里还是 feishu、列表只剩一条。改成把关闭与清空收在一个 closePanel() 出口,选中项 / Esc / 点面板外三条路径都走它。 两份测试: - branchFilter.test.ts —— 把过滤逻辑抽成纯函数 filterBranches 后单测,覆盖 大小写、首尾空格、`/` 中间段、无匹配、不改入参、空搜索词返回同一引用。 抽文件而不是塞进 branchPick.ts:那边管"点中一项产生什么 effect",这边管 "哪些项该显示",出于不同原因变化。 - branchPickerSearch.test.tsx —— jsdom + 真 Radix Popover(不 mock 容器)覆盖 打开即聚焦、实时过滤、无匹配态、Enter 选首项、无匹配时 Enter 不动、点击选中、 关闭清空、roving tabIndex、↓ 进列表、Home/End、loading/failed 两态、 不可交互时不出下拉。为此把 BranchWorktreeChip 导出(仅供测试 render,不是对外 API)。 关于本地门禁的一处更正:pnpm test:unit:related 在本机跑不到 desktop 的 vitest。 test-workspaces.mjs:1077-1082 在 root test runner 非零退出时直接 return,而本机 root runner 因三个环境相关用例必然失败(全局 core.hooksPath 指向别处、 .cindy-worktrees 被扫描、临时目录 ENOTEMPTY),于是 workspace 测试一个都不执行。 本次改为手动补跑等价命令:vitest related --run 那 9 个改动文件(23 passed),外加 related 模式算不到的源码字符串断言型测试(newMakerProjectPicker / newMakerCreateAgentVisualContract / newMakerWorktreeSend / branchPick / pickBranchLabel,132 passed)——它们用 readFileSync 读源码、不 import 组件, vitest 的模块图看不到这层依赖。 Signed-off-by: luojiajun <luo.jj@trip.com>
|
补了单测( 抓到的 bug
复现:搜 改成把关闭与清空收在一个 两份测试
一处需要更正前面的验证说明前面 PR 描述里写的 根因在 if (willRunTestRunner) {
const runnerResult = await runRootTestRunner();
if (runnerResult.exitCode !== 0) {
process.exitCode = 1;
return; // ← workspace 测试一个都不跑
}
}root test runner 非零退出就直接 return。而本机 root runner 因那三个环境相关用例必然失败(全局 补跑的等价命令与结果: CI 上的完整 实机双模式目检仍未做,原因同 PR 描述「未执行的验证」段,未变。 |
|
@wpr317694-del 👋 这个 PR 还有 1 条 review conversation 没 resolve(apps/desktop/src/renderer/components/new-chat/WorktreeChipsRow.tsx),auto-review 因此暂时跳过、没法继续审查 / 合并。 如果你已经按评论改完或回应了,请到对应 thread 上点 Resolve conversation;全部 resolve 后,下一轮 auto-review 会自动重新审查这个 PR。 |
|
命中 UI 路径(apps/desktop/src/renderer/components/new-chat/WorktreeChipsRow.tsx / apps/desktop/src/renderer/components/new-chat/branchFilter.ts)但 description 未附界面效果证据——建议补充改动后效果:截图/录屏,或改动后界面的 HTML 页面(```html 代码块、.html 附件或在线预览链接),便于确认界面符合 DESIGN.md 设计规范 |
|
@wpr317694-del 👋 这个 PR 目前与 请在本地 merge 最新的 |
…arch Signed-off-by: luojiajun <luo.jj@trip.com> # Conflicts: # docs/design-rules/design-inventory.md
冲突已解决 + 补上界面效果证据1. 合并冲突已在本地 merge 最新 冲突只有一个文件: merge 后重跑: 2. 界面效果证据按建议做了改动后界面的 HTML 页面(自包含单文件,无外部资源): 预览: https://htmlpreview.github.io/?https://gist.githubusercontent.com/wpr317694-del/8eea6b760b4dc02aefecc159aa83cb50/raw/branch-picker-preview.html 内容是 Light / Dark 并排,四组:改动前 → 改动后的展开态、输入 需要说明的一点:这是按代码里的实际 class 与 token 值复刻的静态预览,不是实机截图。 所有色值直接取自 HTML 源码(自包含单文件,点开可直接另存为 .html 打开)<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>PR #3844 界面效果 — 分支下拉加搜索框</title>
<style>
/* 全部色值取自 apps/desktop/src/renderer/themes/colors.ts 的 registerColor 默认值,
HSL 形式的 token 已换算成等价 hex。左右两栏 = 同一份结构在 light / dark 下的取值。 */
.light {
--surface: #f8f8f6;
--surface-elevated: #ffffff;
--popover: #ffffff;
--border: #e6e6e6;
--border-default: #d7d7d4;
--foreground: #262626;
--muted-foreground: #737373;
--text-placeholder: #c4c4c4;
--sidebar-item-hover: #e5e5e5;
--accent: #f5f5f5;
--ca-control-bg: #FCFCFC;
--ca-control-border: #DCDFE3;
--ca-control-text: #3C3F43;
}
.dark {
--surface: #1f1f1e;
--surface-elevated: #2c2c2a;
--popover: #272725;
--border: #4a4745;
--border-default: #3c3c3a;
--foreground: #d4d4d4;
--muted-foreground: #a8a29f;
--text-placeholder: #525252;
--sidebar-item-hover: #3c3c3a;
--accent: #363330;
--ca-control-bg: #393838;
--ca-control-border: #434343;
--ca-control-text: #D4D4D4;
}
* { box-sizing: border-box; }
body {
margin: 0;
padding: 28px 20px 56px;
background: #16161a;
color: #e8e8ea;
font: 14px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
}
h1 { font-size: 19px; margin: 0 0 4px; font-weight: 600; }
.sub { color: #9a9aa2; font-size: 13px; margin: 0 0 8px; }
.note {
max-width: 900px; margin: 14px auto 26px; padding: 11px 14px;
border: 1px solid #40404a; border-left: 3px solid #d0a215; border-radius: 8px;
background: #24241f; color: #ded9c4; font-size: 12.5px; line-height: 1.7;
}
.head { max-width: 900px; margin: 0 auto; }
h2 {
max-width: 900px; margin: 34px auto 12px; font-size: 15px; font-weight: 600;
color: #e8e8ea; padding-bottom: 7px; border-bottom: 1px solid #33333c;
}
h2 .cap { display: block; font-weight: 400; font-size: 12.5px; color: #9a9aa2; margin-top: 4px; }
.pair { max-width: 900px; margin: 0 auto; display: flex; gap: 16px; flex-wrap: wrap; }
.pane { flex: 1 1 400px; min-width: 300px; border-radius: 10px; overflow: hidden; border: 1px solid #33333c; }
.pane-hd {
font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
padding: 6px 12px; background: #22222a; color: #8f8f98; border-bottom: 1px solid #33333c;
}
.stage { background: var(--surface); padding: 18px 18px 26px; }
/* ── [⎇ 分支 │ ☑ worktree] 联合控件 (compact 变体, h-30px) ── */
.chip {
display: inline-flex; align-items: stretch; height: 30px; border-radius: 9999px;
overflow: hidden; border: 1px solid var(--ca-control-border);
background: var(--ca-control-bg); color: var(--ca-control-text);
}
.chip-branch {
display: inline-flex; align-items: center; gap: 6px; padding: 0 8px 0 12px;
font-size: 12px; font-weight: 500; max-width: 180px;
}
.chip-div { width: 1px; align-self: center; height: 14px; background: var(--ca-control-border); }
.chip-wt {
display: inline-flex; align-items: center; gap: 6px; padding: 0 12px 0 8px;
font-size: 12px; font-weight: 500;
}
.box {
width: 14px; height: 14px; border-radius: 3px; border: 1.5px solid var(--muted-foreground);
}
svg { display: block; flex: none; }
/* ── 下拉面板 ── */
.panel {
margin-top: 6px; width: 240px; border-radius: 12px; padding: 6px;
border: 1px solid var(--border); background: var(--popover); color: var(--foreground);
box-shadow: 0 10px 15px -3px rgba(0,0,0,.28), 0 4px 6px -4px rgba(0,0,0,.28);
display: flex; flex-direction: column; gap: 6px;
}
.search {
display: flex; align-items: center; gap: 8px; border-radius: 9999px;
border: 1px solid var(--border); background: var(--surface-elevated); padding: 5px 10px;
}
.search .txt { font-size: 13px; color: var(--foreground); }
.search .ph { font-size: 13px; color: var(--text-placeholder); }
.caret {
display: inline-block; width: 1px; height: 14px; background: var(--foreground);
vertical-align: -2px; margin-left: 1px;
}
.list { display: flex; flex-direction: column; max-height: 240px; overflow: hidden; }
.row {
border-radius: 8px; padding: 6px 12px; font-size: 13px; color: var(--foreground);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.row.sel { background: color-mix(in srgb, var(--accent) 60%, transparent); }
.row.hover { background: var(--sidebar-item-hover); }
.row.dim { color: var(--muted-foreground); }
.icon-mut { color: var(--muted-foreground); }
.tag {
display: inline-block; margin-left: 6px; padding: 0 6px; border-radius: 4px;
font-size: 10px; line-height: 16px; vertical-align: 1px;
background: #2f3b4d; color: #8fb6e8; font-weight: 500;
}
.legend { max-width: 900px; margin: 10px auto 0; font-size: 12px; color: #8f8f98; }
.legend code { background: #26262e; padding: 1px 5px; border-radius: 4px; font-size: 11.5px; }
</style>
</head>
<body>
<div class="head">
<h1>PR #3844 — 分支下拉加搜索框</h1>
<p class="sub">apps/desktop/src/renderer/components/new-chat/WorktreeChipsRow.tsx · branchFilter.ts</p>
</div>
<div class="note">
<strong>这是按代码里的实际 class 与 token 值复刻的静态预览,不是实机截图。</strong>
所有色值直接取自 <code>apps/desktop/src/renderer/themes/colors.ts</code> 的 <code>registerColor</code> 默认值(HSL 形式的已换算成等价 hex),
尺寸、圆角、间距按组件里写死的 Tailwind class 还原(面板 <code>rounded-xl</code> + <code>p-1.5</code> + <code>min-w-[200px]</code>,
行 <code>rounded-[8px] px-3 py-1.5 text-13</code>,搜索行 pill + <code>--surface-elevated</code> 填充)。
作者未在本机起 Desktop 目检(原因见 PR 描述「未执行的验证」段),因此这份预览只能证明取值与结构,
不能替代实机观感确认。
</div>
<h2>1. 改动前 → 改动后(默认展开态)
<span class="cap">面板顶部多一行搜索框,打开即聚焦。列表本身的行样式、选中态、面板宽度都没动。</span>
</h2>
<div class="pair">
<div class="pane light">
<div class="pane-hd">Light · 改动前</div>
<div class="stage">
<div class="chip">
<span class="chip-branch">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="6" x2="6" y1="3" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/></svg>
main
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" class="icon-mut"><path d="m6 9 6 6 6-6"/></svg>
</span>
<span class="chip-div"></span>
<span class="chip-wt"><span class="box"></span>worktree</span>
</div>
<div class="panel">
<div class="list">
<div class="row">cindy/auto-k6cgvq</div>
<div class="row">cindy/auto-nv69fk</div>
<div class="row">fix/feishu-ack-emoji</div>
<div class="row sel">main</div>
</div>
</div>
</div>
</div>
<div class="pane dark">
<div class="pane-hd">Dark · 改动后</div>
<div class="stage">
<div class="chip">
<span class="chip-branch">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="6" x2="6" y1="3" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/></svg>
main
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" class="icon-mut"><path d="m6 9 6 6 6-6"/></svg>
</span>
<span class="chip-div"></span>
<span class="chip-wt"><span class="box"></span>worktree</span>
</div>
<div class="panel">
<div class="search">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" class="icon-mut"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
<span class="ph">搜索分支…</span><span class="caret"></span>
</div>
<div class="list">
<div class="row">cindy/auto-k6cgvq</div>
<div class="row">cindy/auto-nv69fk</div>
<div class="row">fix/feishu-ack-emoji</div>
<div class="row sel">main</div>
</div>
</div>
</div>
</div>
</div>
<p class="legend">
左边是改动前的形态(<code>DropdownMenu</code> + 纯列表),右边是改动后(<code>Popover</code> + 搜索行 + 独立滚动列表)。
两栏色板不同是为了同屏对照 light / dark,不代表改动前只有 light。
</p>
<h2>2. 输入 <code style="color:#c8c8d0">feishu</code> 后的过滤态
<span class="cap">大小写不敏感子串匹配。首项带 tabIndex=0(图中以焦点底色示意),其余为 -1。</span>
</h2>
<div class="pair">
<div class="pane light">
<div class="pane-hd">Light</div>
<div class="stage">
<div class="panel">
<div class="search">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" class="icon-mut"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
<span class="txt">feishu</span><span class="caret"></span>
</div>
<div class="list">
<div class="row hover">fix/feishu-ack-emoji<span class="tag">tabIndex 0</span></div>
</div>
</div>
</div>
</div>
<div class="pane dark">
<div class="pane-hd">Dark</div>
<div class="stage">
<div class="panel">
<div class="search">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" class="icon-mut"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
<span class="txt">feishu</span><span class="caret"></span>
</div>
<div class="list">
<div class="row hover">fix/feishu-ack-emoji<span class="tag">tabIndex 0</span></div>
</div>
</div>
</div>
</div>
</div>
<h2>3. 搜中间段 <code style="color:#c8c8d0">cindy/auto</code>
<span class="cap"><code>/</code> 参与匹配,可以直接搜命名空间中间段。</span>
</h2>
<div class="pair">
<div class="pane light">
<div class="pane-hd">Light</div>
<div class="stage">
<div class="panel">
<div class="search">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" class="icon-mut"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
<span class="txt">cindy/auto</span><span class="caret"></span>
</div>
<div class="list">
<div class="row hover">cindy/auto-k6cgvq<span class="tag">tabIndex 0</span></div>
<div class="row">cindy/auto-nv69fk</div>
</div>
</div>
</div>
</div>
<div class="pane dark">
<div class="pane-hd">Dark</div>
<div class="stage">
<div class="panel">
<div class="search">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" class="icon-mut"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
<span class="txt">cindy/auto</span><span class="caret"></span>
</div>
<div class="list">
<div class="row hover">cindy/auto-k6cgvq<span class="tag">tabIndex 0</span></div>
<div class="row">cindy/auto-nv69fk</div>
</div>
</div>
</div>
</div>
</div>
<h2>4. 三种非常规态:无匹配 / 加载中 / 加载失败
<span class="cap">无匹配与加载失败是两套文案,不复用。失败行可点,点了不关面板。</span>
</h2>
<div class="pair">
<div class="pane light">
<div class="pane-hd">Light</div>
<div class="stage">
<div class="panel" style="margin-top:0">
<div class="search">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" class="icon-mut"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
<span class="txt">zzz</span><span class="caret"></span>
</div>
<div class="list"><div class="row dim">没有匹配的分支</div></div>
</div>
<div class="panel">
<div class="search">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" class="icon-mut"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
<span class="ph">搜索分支…</span>
</div>
<div class="list"><div class="row dim">加载分支中…</div></div>
</div>
<div class="panel">
<div class="search">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" class="icon-mut"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
<span class="ph">搜索分支…</span>
</div>
<div class="list"><div class="row dim">分支列表加载失败,点此重试</div></div>
</div>
</div>
</div>
<div class="pane dark">
<div class="pane-hd">Dark</div>
<div class="stage">
<div class="panel" style="margin-top:0">
<div class="search">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" class="icon-mut"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
<span class="txt">zzz</span><span class="caret"></span>
</div>
<div class="list"><div class="row dim">没有匹配的分支</div></div>
</div>
<div class="panel">
<div class="search">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" class="icon-mut"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
<span class="ph">搜索分支…</span>
</div>
<div class="list"><div class="row dim">加载分支中…</div></div>
</div>
<div class="panel">
<div class="search">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" class="icon-mut"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
<span class="ph">搜索分支…</span>
</div>
<div class="list"><div class="row dim">分支列表加载失败,点此重试</div></div>
</div>
</div>
</div>
</div>
<h2>对照的设计规范条款</h2>
<div class="legend" style="max-width:900px;line-height:1.9">
<div><strong style="color:#c8c8d0">DESIGN.md §4 Inputs & Forms</strong>(<code>:221-231</code>)— 搜索行走单行输入规范:pill 圆角、<code>--surface-elevated</code> 填充、<code>--border-default</code> 1px 描边、placeholder 用 <code>--text-placeholder</code>(该节点名禁止 Silver <code>#a3a3a3</code>,本次未引入任何写死色值)。</div>
<div><strong style="color:#c8c8d0">DESIGN.md §4 Select & Dropdown</strong>(<code>:236-242</code>)— 面板 12px 圆角 + Card bg,选项行 8px 内层圆角,内层半径小于容器半径。该节还要求面板宽度绑定 trigger 宽度;这个菜单<strong>改动前</strong>用的就是 <code>min-w-[200px]</code> 而非绑定值,本次只在面板内加了一行控件,未新增违规,也没顺手改(会牵动 compact / 非 compact 两套 chip 宽度)。</div>
<div><strong style="color:#c8c8d0">DESIGN.md §10 双模式交付门槛</strong>(<code>:382-387</code>)— 新增的搜索行、无匹配提示、列表行 hover / focus-visible 态全部走语义 token,无单模式硬编码。按该节最后一条:目检未做,不把「复用了 themed 样式」当成「双模式已验证」。</div>
</div>
</body>
</html> |
|
@wpr317694-del 👋 这个 PR 目前与 请在本地 merge 最新的 |
|
@wpr317694-del 👋 这个 PR 现在在等维护者确认,确认之前流程不会合并它 —— 不是卡住了,也不是在等你再改一版(你推的改动流程都读到了,判的就是最新一版代码)。
这条是流程自动发的状态提醒(同一版代码只发一次),不用回复。 |
这次改了什么
摘要
新建会话页的
[⎇ 分支 │ ☑ worktree]联合控件,左半分支区点开后是个纯列表,分支多了只能逐条肉眼找。这里在下拉顶部加了一个搜索输入框:打开即聚焦,按分支名做大小写不敏感的子串过滤,Enter 选第一个匹配项,↑↓ 在列表内移动,关闭时清空搜索词。顺带把这个下拉的容器从 Radix
DropdownMenu换成了Popover。原因是 menu 自带两条跟「面板里有个输入框」直接冲突的行为:@radix-ui/react-menu的Content.onKeyDown对任意单字符按键调handleTypeaheadSearch,打字会被拿去做菜单项跳转。这条能用stopPropagation挡掉。Item.onPointerMove会把焦点抢到鼠标掠过的那一项 —— 打字时鼠标从列表上方经过就丢焦点,后续按键回落 typeahead。这条挡不掉。仓内另两处「下拉里带搜索」的实现(
OneshotModelPinPicker、ScriptCapabilityMultiSelect)同样是 Popover +onOpenAutoFocus,本次与它们对齐。变更类型
feat新功能范围
origin/*)不纳入列表 —— 要改 main 侧listBranches、IPC 契约、device-link 隧道,还得回答「选了远程分支怎么建 worktree」,不在本次范围;分支排序不动;worktree 勾选半区完全不动rounded-xl保持不变,宽度上限 320px(原来只有min-w-[200px]无上限)UI 变化
未附截图,原因见「未执行的验证」。
docs/design-rules/DESIGN.md§4「Inputs & Forms」 — 搜索行按单行输入规范:pill 圆角(rounded-full)、--surface-elevated填充、--border-default1px 描边、placeholder 走--text-placeholder(该节明确禁止用 Silver#a3a3a3,本次未引入任何写死色值)。docs/design-rules/DESIGN.md§4「Select & Dropdown」 — 面板 12px 圆角(rounded-xl)+ Card bg,选项行用 8px 内层圆角(rounded-[8px]),内层半径小于容器半径。该节还要求面板宽度绑定 trigger 宽度;这个菜单改动前用的就是min-w-[200px]而非绑定值,本次只在面板内部加了一行控件、没有改这条既有形态,未新增违规,也没顺手去改(会牵动 compact / 非 compact 两套 chip 宽度,超出本 PR 范围)。docs/design-rules/DESIGN.md§10「Light / Dark Dual-Mode Delivery Gate」 — 新增的搜索行、空匹配提示、列表行 hover / focus-visible 态全部走语义 token(--surface-elevated/--text-placeholder/border-border/bg-sidebar-item-hover/text-muted-foreground),无单模式硬编码、无条件补丁,两种模式由 token 系统覆盖。按该节最后一条:目检未做,不把「复用了 themed 样式」当成「双模式已验证」。怎么验证的
自动验证
失败基线(在干净的
origin/main(ec4bd8633)上 stash 掉本 PR 全部改动后跑同一批测试):三个基线失败的本机原因,逐条有直接证据:
check-dco.test.mjsinstaller 用例core.hooksPath被指向别处,测试读到全局值/Users/ccsa/.claude/ocr-local/githooks与git config --global core.hooksPath输出逐字符一致migration-freeze.test.mjscleanup()在rmSync临时 git 仓时ENOTEMPTYENOTEMPTY: directory not empty, rmdir '/var/folders/.../cindy-migration-freeze-*/.git';该文件只读 drizzle migration 与scripts/,与 renderer 无依赖路径。并发下失败条数会从 1 条涨到 3 条test-workspaces.test.mjssymlink 用例.cindy-worktrees/目录被扫进来.cindy-worktrees/auto-k6cgvq/与.cindy-worktrees/auto-nv69fk/开头,ls -d .cindy-worktrees/*正是这两个手工验证
未执行。
未执行的验证
pnpm restart:desktop:remote会先关掉当前运行的 Desktop 实例,而提交这个 PR 的会话本身就跑在同一台机器的 Cindy Desktop 里,起 dev 实例有中断当前会话的风险,没有在未确认的情况下执行。因此双模式实际观感未经验证,只能说明新增样式全部走语义 token、未引入单模式硬编码。合并前建议由 reviewer 或作者补一次两模式目检 + 截图。pnpm test:all:本次改动只在 renderer 单文件 + i18n json + 一份生成的文档台账,无跨模块、无基础设施改动。CI 的完整pnpm test:unit仍会跑。风险
风险分类
影响与回滚
useBranches数据链、resolveBranchPick、onSourceBranchChange、worktree 勾选、发送门禁、device-link 远程分支拉取一律未碰;main 进程 / IPC 契约 / preload / device-link allowlist 零改动,无跨端 wire protocol 变化;改动全在apps/desktop,不进 mobile runtime fingerprint。