fix(issues): polish comment trigger chip presentation (#4002)

- Copy: one fixed sentence for single and stacked chips — the avatar(s)
  carry who and how many, the text carries condition + outcome
  ("发送后开始工作" / "Starts working when sent"), killing the
  "is it already running?" misread. Drops the per-name and count keys.
- Color: sidebar-style resting state — muted-foreground until hover so
  the strip reads as metadata, not content.
- Motion: pure fade-in (no slide offset).
- Spacing: reply composer reserves pb-9 so the chip strip reads as a
  footer instead of a second content line glued to the text.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Naiyuan Qing
2026-06-10 17:09:03 +08:00
committed by GitHub
parent 619c4c4953
commit dc129b1178
7 changed files with 26 additions and 29 deletions

View File

@@ -45,14 +45,14 @@ describe("CommentTriggerChips", () => {
);
const chip = screen.getByRole("button");
expect(chip).toHaveTextContent("Walt will start working");
expect(chip).toHaveTextContent("Starts working when sent");
expect(chip).toHaveAttribute("aria-pressed", "false");
fireEvent.click(chip);
expect(onToggle).toHaveBeenCalledWith("agent-1");
});
it("dims a suppressed single agent into the skip sentence", () => {
it("dims a suppressed single agent into the skip state", () => {
renderWithI18n(
<CommentTriggerChips
agents={[walt]}
@@ -62,11 +62,11 @@ describe("CommentTriggerChips", () => {
);
const chip = screen.getByRole("button");
expect(chip).toHaveTextContent("Walt won't be triggered this time");
expect(chip).toHaveTextContent("not this time");
expect(chip).toHaveAttribute("aria-pressed", "true");
});
it("collapses several agents into a stack with an active count", () => {
it("collapses several agents into a stack with the shared sentence", () => {
renderWithI18n(
<CommentTriggerChips
agents={[walt, bob]}
@@ -75,19 +75,19 @@ describe("CommentTriggerChips", () => {
/>,
);
expect(screen.getByRole("button")).toHaveTextContent("2 agents will start working");
expect(screen.getByRole("button")).toHaveTextContent("Starts working when sent");
});
it("counts only non-suppressed agents in the sentence", () => {
it("switches to the skip state when every agent is suppressed", () => {
renderWithI18n(
<CommentTriggerChips
agents={[walt, bob]}
suppressedAgentIds={new Set(["agent-2"])}
suppressedAgentIds={new Set(["agent-1", "agent-2"])}
onToggle={vi.fn()}
/>,
);
expect(screen.getByRole("button")).toHaveTextContent("1 agent will start working");
expect(screen.getByRole("button")).toHaveTextContent("not this time");
});
it("opens the popover on click and toggles a row", () => {

View File

@@ -148,9 +148,11 @@ function SingleTriggerChip({
t: IssuesT;
}) {
const state = suppressed ? t(($) => $.comment.trigger_suppressed) : sourceLabel(agent.source, t);
// The avatar carries "who"; the sentence carries only condition + outcome,
// so it stays fixed-width and never truncates on long agent names.
const sentence = suppressed
? t(($) => $.comment.trigger_will_skip, { name: agent.name })
: t(($) => $.comment.trigger_will_start, { name: agent.name });
? t(($) => $.comment.trigger_suppressed)
: t(($) => $.comment.trigger_will_start);
return (
<Tooltip>
@@ -162,8 +164,10 @@ function SingleTriggerChip({
aria-label={t(($) => $.comment.trigger_chip_aria, { name: agent.name, state })}
onClick={() => onToggle(agent.id)}
className={cn(
"inline-flex h-6 min-w-0 max-w-full animate-in fade-in slide-in-from-bottom-1 cursor-pointer items-center gap-1.5 rounded-md px-1.5 text-[11px] font-medium transition-colors duration-200 hover:bg-muted hover:text-foreground",
suppressed ? "text-muted-foreground opacity-60" : "text-foreground",
// Sidebar-style resting state: muted until hover so the strip
// reads as metadata, not content (see app-sidebar nav items).
"inline-flex h-6 min-w-0 max-w-full animate-in fade-in cursor-pointer items-center gap-1.5 rounded-md px-1.5 text-[11px] font-medium text-muted-foreground transition-colors duration-200 hover:bg-muted hover:text-foreground",
suppressed && "opacity-60",
)}
>
<TriggerAgentAvatar agent={agent} suppressed={suppressed} />
@@ -197,10 +201,12 @@ function MultiTriggerChip({
// Mirror AgentAvatarStack: ~30% overlap reads as "stacked" without
// obscuring the next avatar.
const overlap = Math.round(AVATAR_SIZE * 0.3);
// The avatar stack already shows who and how many — the sentence is the
// same fixed condition + outcome copy as the single chip.
const sentence =
activeCount === 0
? t(($) => $.comment.trigger_suppressed)
: t(($) => $.comment.trigger_will_start_count, { count: activeCount });
: t(($) => $.comment.trigger_will_start);
const popoverTrigger = (
<PopoverTrigger
@@ -210,8 +216,8 @@ function MultiTriggerChip({
<button
type="button"
className={cn(
"inline-flex h-6 min-w-0 max-w-full animate-in fade-in slide-in-from-bottom-1 cursor-pointer items-center gap-1.5 rounded-md px-1.5 text-[11px] font-medium transition-colors duration-200 hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground",
activeCount === 0 ? "text-muted-foreground opacity-60" : "text-foreground",
"inline-flex h-6 min-w-0 max-w-full animate-in fade-in cursor-pointer items-center gap-1.5 rounded-md px-1.5 text-[11px] font-medium text-muted-foreground transition-colors duration-200 hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground",
activeCount === 0 && "opacity-60",
)}
/>
}

View File

@@ -155,7 +155,7 @@ function ReplyInput({
{...dropZoneProps}
className={cn(
"relative min-w-0 flex-1 flex flex-col",
!isEmpty && "pb-7",
!isEmpty && "pb-9",
)}
>
<div className="flex-1 min-h-0 overflow-y-auto">

View File

@@ -284,10 +284,7 @@
"trigger_reason_mention_agent": "{{name}} is mentioned in this comment.",
"trigger_reason_mention_squad_leader": "{{name}} leads a squad mentioned in this comment.",
"trigger_reason_unknown": "{{name}} will be triggered by this comment.",
"trigger_will_start": "{{name}} will start working",
"trigger_will_skip": "{{name}} won't be triggered this time",
"trigger_will_start_count_one": "{{count}} agent will start working",
"trigger_will_start_count_other": "{{count}} agents will start working",
"trigger_will_start": "Starts working when sent",
"trigger_starts_now": "It will start working right away.",
"trigger_starts_when_online": "It is offline now and will start once online.",
"trigger_click_to_skip": "Click to skip triggering this time.",

View File

@@ -276,9 +276,7 @@
"trigger_reason_mention_agent": "このコメントで {{name}} がメンションされています。",
"trigger_reason_mention_squad_leader": "{{name}} はメンションされた Squad のリーダーです。",
"trigger_reason_unknown": "このコメントで {{name}} がトリガーされます。",
"trigger_will_start": "{{name}} が作業を開始します",
"trigger_will_skip": "{{name}} は今回トリガーされません",
"trigger_will_start_count_other": "{{count}} 体のエージェントが作業を開始します",
"trigger_will_start": "送信後に作業を開始します",
"trigger_starts_now": "すぐに作業を開始します。",
"trigger_starts_when_online": "現在オフラインのため、オンラインになり次第開始します。",
"trigger_click_to_skip": "クリックすると今回はトリガーされません。",

View File

@@ -284,9 +284,7 @@
"trigger_reason_mention_agent": "이 댓글에서 {{name}}님을 멘션했습니다.",
"trigger_reason_mention_squad_leader": "{{name}}님은 멘션된 Squad의 리더입니다.",
"trigger_reason_unknown": "이 댓글로 {{name}}님이 트리거됩니다.",
"trigger_will_start": "{{name}}이(가) 작업을 시작합니다",
"trigger_will_skip": "{{name}}은(는) 이번에 트리거되지 않습니다",
"trigger_will_start_count_other": "에이전트 {{count}}개가 작업을 시작합니다",
"trigger_will_start": "전송 후 작업을 시작합니다",
"trigger_starts_now": "바로 작업을 시작합니다.",
"trigger_starts_when_online": "현재 오프라인이며 온라인이 되면 시작합니다.",
"trigger_click_to_skip": "클릭하면 이번에는 트리거되지 않습니다.",

View File

@@ -281,9 +281,7 @@
"trigger_reason_mention_agent": "这条评论 @ 了 {{name}}。",
"trigger_reason_mention_squad_leader": "{{name}} 是被 @ Squad 的 leader。",
"trigger_reason_unknown": "这条评论会触发 {{name}}。",
"trigger_will_start": "{{name}} 将开始工作",
"trigger_will_skip": "{{name}} 本次不触发",
"trigger_will_start_count_other": "{{count}} 个智能体将开始工作",
"trigger_will_start": "发送后开始工作",
"trigger_starts_now": "将立即开始工作。",
"trigger_starts_when_online": "当前离线,上线后开始。",
"trigger_click_to_skip": "点击后本次不触发。",