🐛 fix(vscode): 悬停提示不用原生 title,webview 统一用自绘提示框,代码阅读的种类图标停上去能看到种类名

VS Code 1.139 带的 Electron 43 在 macOS 上只在鼠标压着带 title 的元素自身边缘时才弹原生提示,
停在它里面的图标、文字上不弹(electron/electron#49843,Electron 38 起)。种类图标 20px 的框
几乎被 16px 的图形占满,所以基本看不到种类名;块右上角按钮同样受影响。页面上核对过:图标中心点
命中的是图标里的线条,没有盖在上面的东西。

- components/ui/tooltip.tsx 改成整页只挂一次的 TooltipHost:元素只写 data-tooltip,事件统一交给它;
  样子照 VS Code 悬停框(editorHoverWidget 那组变量、widget-shadow 阴影),停 500ms 才弹、弹着时换元素
  立刻换,移开 / 按下 / 按键 / 滚动收起;键盘聚焦立刻弹,提示不是元素自己的名字时挂 aria-describedby
- 代码阅读里所有 title(种类图标、块按钮、筛选栏、问答区、中文名的「原名」)改成 data-tooltip;
  SelectMenu 的 title 属性改名 tooltip;看板工单详情原来包一层的 Tooltip 也改成 data-tooltip
- 何时弹 / 收的规则抽成纯函数加测试;更新代码阅读 spec
This commit is contained in:
cruldra committed 2026-09-27 21:21:13 +08:00
1 parent 15790ce7c8
commit ec34ace7cf
17 files changed
+399 -187

No files matched your search

+10 -10
View File
@@ -19,11 +19,11 @@ function Thumbs({ urls, pending, onOpen, onRemove }: { urls: string[], pending?:
{urls.map((url, index) => (
// 同一张图可能贴两次、地址一样;缩略图没有自己的状态,按位置认就行
<span key={index} className="cr-qa-thumb">
<button type="button" className="cr-qa-thumb-open" title="看大图" aria-label={`看第 ${index + 1} 张图片的大图`} onClick={() => onOpen(url)}>
<button type="button" className="cr-qa-thumb-open" data-tooltip="看大图" aria-label={`看第 ${index + 1} 张图片的大图`} onClick={() => onOpen(url)}>
<img src={url} alt="" />
</button>
{onRemove && (
<button type="button" className="cr-qa-thumb-remove" title="去掉这张图片" aria-label={`去掉第 ${index + 1} 张图片`} onClick={() => onRemove(index)}>
<button type="button" className="cr-qa-thumb-remove" data-tooltip="去掉这张图片" aria-label={`去掉第 ${index + 1} 张图片`} onClick={() => onRemove(index)}>
<X size={10} strokeWidth={2.4} aria-hidden />
</button>
)}
@@ -55,7 +55,7 @@ function ImagePreview({ url, onClose }: { url: string, onClose: () => void }): R
return createPortal(
<div className="cr-qa-lightbox" role="dialog" aria-modal="true" aria-label="图片大图" onClick={onClose} onKeyDown={onKeyDown}>
<img src={url} alt="" />
<button ref={close} type="button" className="cr-qa-lightbox-close" title="关闭(Esc)" aria-label="关闭大图">
<button ref={close} type="button" className="cr-qa-lightbox-close" data-tooltip="关闭(Esc)" aria-label="关闭大图">
<X size={16} strokeWidth={1.8} aria-hidden />
</button>
</div>,
@@ -71,16 +71,16 @@ function HistoryItem({ entry, now, onDelete, onOpenImage }: { entry: QaEntryView
<div className="cr-qa-entry" data-history="">
<div className="cr-qa-head">
<Sparkles size={12} strokeWidth={1.8} className="cr-qa-icon" aria-hidden />
<button type="button" className="cr-qa-question" aria-expanded={expanded} title={expanded ? '收起回答' : '展开整段回答'} onClick={toggle}>
<button type="button" className="cr-qa-question" aria-expanded={expanded} data-tooltip={expanded ? '收起回答' : '展开整段回答'} onClick={toggle}>
{entry.question}
</button>
<span className="cr-qa-when">
<time dateTime={entry.createdAt} title={new Date(entry.createdAt).toLocaleString()}>
<time dateTime={entry.createdAt} data-tooltip={new Date(entry.createdAt).toLocaleString()}>
{relativeTime(entry.createdAt, now)}
</time>
{entry.profile && <span title="用这个 Claude Code 配置(profile)答的">{` · ${entry.profile}`}</span>}
{entry.profile && <span data-tooltip="用这个 Claude Code 配置(profile)答的">{` · ${entry.profile}`}</span>}
</span>
<button type="button" className="cr-tool cr-qa-delete" title="已理解,删除" aria-label="已理解,删除" onClick={onDelete}>
<button type="button" className="cr-tool cr-qa-delete" data-tooltip="已理解,删除" aria-label="已理解,删除" onClick={onDelete}>
<Trash2 size={13} strokeWidth={1.8} aria-hidden />
</button>
</div>
@@ -218,17 +218,17 @@ function AskInput({ running, first, profiles, onSelectProfile, onAsk, onStop, on
options={profiles.options.map(option => ({ value: option.id, label: option.label }))}
onChange={onSelectProfile}
placeholder="默认"
title="用哪个 Claude Code 配置(profile)回答,下一个问题起用它"
tooltip="用哪个 Claude Code 配置(profile)回答,下一个问题起用它"
className="cr-qa-profile"
/>
{running
? (
<button type="button" className="cr-tool" title="停止" aria-label="停止" onClick={() => onStop(running.askId)}>
<button type="button" className="cr-tool" data-tooltip="停止" aria-label="停止" onClick={() => onStop(running.askId)}>
<CircleStop size={14} strokeWidth={1.8} aria-hidden />
</button>
)
: (
<button type="button" className="cr-tool" title="发送(Enter)" aria-label="发送" disabled={value.trim() === ''} onClick={send}>
<button type="button" className="cr-tool" data-tooltip="发送(Enter)" aria-label="发送" disabled={value.trim() === ''} onClick={send}>
<SendHorizontal size={14} strokeWidth={1.8} aria-hidden />
</button>
)}