DSH / Atlas
2026-08-26implementedfeaturearchived 2026-09-04

Web trigger menu presentation polish

Web 触发菜单呈现打磨

The Web composer's `/` and `@` trigger menu carried several presentation defects that made the reference flow harder to read and operate. Candidate rows spelled their kind as a localized text prefix (`Folder · name/`, `Session · label`) that duplicated the section title and pushed the name right. Pointer hover used a CSS `:hover` tint while keyboard navigation drove the reducer-owned highlight, so two rows could look

English

Problem

The Web composer's / and @ trigger menu carried several presentation defects that made the reference flow harder to read and operate. Candidate rows spelled their kind as a localized text prefix (Folder · name/, Session · label) that duplicated the section title and pushed the name right. Pointer hover used a CSS :hover tint while keyboard navigation drove the reducer-owned highlight, so two rows could look focused at once. The drillable-folder affordance was a raw › text glyph, unlike every other chevron in the composer, and nothing told the user that Tab drills into the highlighted folder. The pending-source state was a bare "Loading…" text row. The editable @dir/ text a drill leaves behind rendered a folder icon before the @, visually double-marking a token that is not a settled chip. The composer placeholders never mentioned that / and @ exist (#3080).

Decision

Candidate rows lead with a domain icon instead of a text prefix: InputTriggerCandidate.icon narrows from string to the closed union InputTriggerCandidateIcon (file | folder | session), the menu view maps it to ReferenceIcon, and ui-reference emits bare names (folderx/, session label). The candidate.file/candidate.folder/candidate.session locale keys are deleted; the session section title is 对话/Sessions. The menu spans the composer card edge to edge (left: 0; right: 0), and a pending source renders two breathing skeleton bars in item cell metrics instead of the loading text row.

Pointer and keyboard share one highlight, last input wins: a hover MenuEvent parks the reducer-owned highlight on a ready row, MenuView routes it from onMouseMove (not mouseenter, so keyboard-scrolling rows under a resting pointer cannot steal the highlight back), and the CSS :hover tint is gone.

The drill affordance on the highlighted folder row is the library IconChevronRightOutline14 in the quiet --dsw-alias-label-caption tint the access-mode chevron uses, preceded by a localized "Browse folder" caption and a Tab keycap that reveal only while the row holds the shared highlight.

A token still carrying its trigger character is editable text, not a settled chip: the text-ref decoration colors it and nothing more, and the domain icon belongs exclusively to the settled ReferenceChipNode. The former appearance channel (scan appearance field, TextRefNode.__appearance, data-ref-appearance DOM attribute, CSS ::before icon) is deleted end to end.

Composer placeholders advertise both triggers (描述你想要构建的内容… / 调用指令 @ 文件或对话 / Describe what you want to build... / commands, @ files or sessions), and the zh copy for commands is unified from 命令 to 指令 across ui-chat, ui-conversation, ui-goal, and ui-input-trigger.

Alternatives considered

Keep the CSS :hover tint alongside the keyboard highlight. Rejected: two rows can look focused at once while aria-activedescendant names only one, and Enter acts on the keyboard row while the eye may rest on the hovered one.

Route hover from mouseenter. Rejected: when arrow keys scroll new rows under a stationary pointer, each row entering the pointer re-fires mouseenter and steals the highlight the user just moved; mousemove fires only on real pointer motion.

Keep the folder icon on the editable @dir/ text. Rejected: the icon before the trigger character double-marks the token and erases the visual distinction between "still editable text" and "settled chip"; reserving the icon for the chip makes the two states readable at a glance.

Show the Tab hint on every drillable row. Rejected: idle rows carrying persistent keycaps add noise; the hint teaches the key exactly when it applies — while that row is the one Tab would act on.

Consequences

The kind information every row used to spell in text now rides the icon and section title; a future candidate kind must extend InputTriggerCandidateIcon and pick an icon rather than pass an arbitrary string. Pointer motion round-trips through the reducer (hover is a no-op for the already-highlighted row, so mousemove storms do not churn state). Drill discoverability rests on the highlight: an idle folder row shows only its chevron until hovered or reached by keys. Deferred follow-ups — settle-on-space for exact-match tokens and name vs name/ labels — remain tracked in #3154; candidate description content, back navigation after a drill, and reference search latency are settled by the @ mention discovery and row content note.

中文

Problem

Web composer 的 / 与 @ 触发菜单存在多处呈现缺陷,使引用流程更难阅读和操作。候选行用本地化文字前缀标注类型(Folder · name/、Session · label),既与 section 标题重复又把名称挤向右侧。指针悬停用 CSS :hover 着色,而键盘导航驱动 reducer 持有的高亮,两行可能同时呈现焦点态。可下钻文件夹的操作标记是裸文本 ›,与 composer 中其他 chevron 不一致,且没有任何提示告诉用户 Tab 可以下钻高亮的文件夹。来源加载中状态是一行"正在加载…"文字。下钻留下的可编辑 @dir/ 文本在 @ 前渲染文件夹图标,对一个并非 settled chip 的 token 形成视觉双重标记。composer 的 placeholder 从未提及 / 和 @ 的存在(#3080)。

Decision

候选行以领域图标开头,不再用文字前缀:InputTriggerCandidate.icon 从 string 收窄为封闭联合 InputTriggerCandidateIcon(file | folder | session),菜单视图将其映射到 ReferenceIcon,ui-reference 只输出裸名称(folderx/、session 标签)。删除 candidate.file/candidate.folder/candidate.session 三个 locale 键;session section 标题改为 对话/Sessions。菜单与 composer 卡片左右等宽(left: 0; right: 0),加载中的来源渲染两条与候选行同尺寸的呼吸骨架条,替代加载文字行。

指针与键盘共享单一高亮,后到者优先:新增 hover MenuEvent 把 reducer 持有的高亮停在某个就绪行上,MenuView 从 onMouseMove 路由(不用 mouseenter,避免键盘滚动把新行送到静止指针下时抢回高亮),CSS :hover 着色整体移除。

高亮文件夹行上的下钻标记改为库内 IconChevronRightOutline14,使用访问模式 chevron 同款的弱色 --dsw-alias-label-caption,左侧为本地化"进入目录"文字加 Tab 键帽提示,仅当该行持有共享高亮时显示。

仍带触发符的 token 是可编辑文本而非 settled chip:text-ref 装饰只做染色,领域图标专属于 settled 的 ReferenceChipNode。原有的 appearance 通道(扫描的 appearance 字段、TextRefNode.__appearance、data-ref-appearance DOM 属性、CSS ::before 图标)端到端删除。

composer placeholder 同时提示两个触发符(描述你想要构建的内容… / 调用指令 @ 文件或对话 / Describe what you want to build... / commands, @ files or sessions),并将 ui-chat、ui-conversation、ui-goal、ui-input-trigger 中命令的中文文案统一为"指令"。

Alternatives considered

保留 CSS :hover 着色与键盘高亮并存。 被否:两行可能同时呈现焦点态,而 aria-activedescendant 只指向一行;Enter 作用于键盘行,视线却可能停在悬停行上。

从 mouseenter 路由悬停。 被否:方向键把新行滚动到静止指针下方时,每个进入指针的行都会重新触发 mouseenter,抢走用户刚移走的高亮;mousemove 只在指针真实移动时触发。

保留可编辑 @dir/ 文本上的文件夹图标。 被否:触发符前的图标对 token 形成双重标记,抹掉了"仍可编辑的文本"与"settled chip"之间的视觉区分;图标专属于 chip 才能让两种状态一眼可辨。

在所有可下钻行上常驻 Tab 提示。 被否:空闲行常驻键帽增加噪音;提示恰好在其生效时出现——该行正是 Tab 将作用的行。

Consequences

过去每行用文字拼写的类型信息现在由图标和 section 标题承载;未来新增候选类型必须扩展 InputTriggerCandidateIcon 并选定图标,而非传任意字符串。指针移动经 reducer 往返(hover 对已高亮行是 no-op,mousemove 风暴不会搅动状态)。下钻的可发现性依赖高亮:空闲文件夹行在被悬停或键盘到达前只显示 chevron。延后的跟进项——精确匹配 token 的空格 settle、name 与 name/ 标签——仍记录在 #3154;候选 description 内容、下钻后的回退导航与引用搜索延迟由 @ mention 发现与行内容笔记 结清。