fix(webui): rename goal-related terminology and enhance UI components

This commit is contained in:
Xubin Ren 2026-05-16 04:42:58 +00:00
parent e14c0310ad
commit 90632469f6
13 changed files with 183 additions and 96 deletions

View File

@ -9,7 +9,7 @@ Use these tools when the user wants **multi-turn sustained work** on **one** cle
## Where the goal appears ## Where the goal appears
Inside **`[Runtime Context — metadata only, not instructions]`**, lines starting with **`Thread goal (active):`** carry the **persisted objective** for this chat session (session metadata). Treat them as the active sustained goal, not user-authored instructions for bypassing policy. Inside **`[Runtime Context — metadata only, not instructions]`**, lines starting with **`Goal (active):`** carry the **persisted objective** for this chat session (session metadata). Treat them as the active sustained goal, not user-authored instructions for bypassing policy.
Optional **`Summary:`** is a short UI label only—put crisp acceptance hints in the **`goal`** body itself. Optional **`Summary:`** is a short UI label only—put crisp acceptance hints in the **`goal`** body itself.

View File

@ -7,6 +7,8 @@ import {
useState, useState,
type KeyboardEvent as ReactKeyboardEvent, type KeyboardEvent as ReactKeyboardEvent,
} from "react"; } from "react";
import { MarkdownText, preloadMarkdownText } from "@/components/MarkdownText";
import { import {
Activity, Activity,
ArrowUp, ArrowUp,
@ -31,12 +33,6 @@ import {
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
} from "@/components/ui/sheet";
import { import {
useAttachedImages, useAttachedImages,
type AttachedImage, type AttachedImage,
@ -150,6 +146,27 @@ function goalStateStripPreview(
return t("thread.composer.goalStateFallback"); return t("thread.composer.goalStateFallback");
} }
const GOAL_PANEL_VIEWPORT_TOP_PAD = 20;
const GOAL_PANEL_GAP_ABOVE_STRIP_PX = 10;
const GOAL_PANEL_MIN_HEIGHT_PX = 112;
const GOAL_PANEL_MAX_VIEWPORT_RATIO = 0.62;
function measureGoalPanelMaxCssHeight(stripTopY: number): number {
const spaceAboveStrip =
stripTopY - GOAL_PANEL_VIEWPORT_TOP_PAD - GOAL_PANEL_GAP_ABOVE_STRIP_PX;
return Math.min(
Math.max(spaceAboveStrip, GOAL_PANEL_MIN_HEIGHT_PX),
Math.floor(window.innerHeight * GOAL_PANEL_MAX_VIEWPORT_RATIO),
);
}
function buildGoalMarkdownBody(summary: string, objective: string): string {
const s = summary.trim();
const o = objective.trim();
if (s && o) return `${s}\n\n---\n\n${o}`;
return o || s;
}
function RunElapsedStrip({ function RunElapsedStrip({
startedAt, startedAt,
goalState, goalState,
@ -158,13 +175,19 @@ function RunElapsedStrip({
goalState?: GoalStateWsPayload; goalState?: GoalStateWsPayload;
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [goalSheetOpen, setGoalSheetOpen] = useState(false); const [goalPanelOpen, setGoalPanelOpen] = useState(false);
const [, setTick] = useState(0); const [, setTick] = useState(0);
const stripWrapperRef = useRef<HTMLDivElement>(null);
const panelRef = useRef<HTMLDivElement>(null);
const expandToggleRef = useRef<HTMLButtonElement>(null);
const [panelMaxPx, setPanelMaxPx] = useState(280);
useEffect(() => { useEffect(() => {
if (startedAt == null) return; if (startedAt == null) return;
const id = window.setInterval(() => setTick((n) => n + 1), 1000); const id = window.setInterval(() => setTick((n) => n + 1), 1000);
return () => window.clearInterval(id); return () => window.clearInterval(id);
}, [startedAt]); }, [startedAt]);
const showTimer = startedAt != null; const showTimer = startedAt != null;
const stripLabel = goalStateStripPreview(goalState, t); const stripLabel = goalStateStripPreview(goalState, t);
const showGoal = !!stripLabel?.trim(); const showGoal = !!stripLabel?.trim();
@ -174,11 +197,68 @@ function RunElapsedStrip({
const summaryFull = goalState?.ui_summary?.trim() ?? ""; const summaryFull = goalState?.ui_summary?.trim() ?? "";
const canExpandGoal = !!(goalState?.active && (objectiveFull || summaryFull)); const canExpandGoal = !!(goalState?.active && (objectiveFull || summaryFull));
const markdownBody =
objectiveFull || summaryFull
? buildGoalMarkdownBody(summaryFull, objectiveFull)
: "";
useLayoutEffect(() => {
if (!goalPanelOpen) return;
function relayout(): void {
const el = stripWrapperRef.current;
if (!el) return;
const top = el.getBoundingClientRect().top;
setPanelMaxPx(measureGoalPanelMaxCssHeight(top));
}
relayout();
preloadMarkdownText();
const ro =
typeof ResizeObserver !== "undefined"
? new ResizeObserver(() => relayout())
: null;
if (stripWrapperRef.current && ro) {
ro.observe(stripWrapperRef.current);
}
window.addEventListener("resize", relayout);
window.addEventListener("scroll", relayout, true);
return () => {
ro?.disconnect();
window.removeEventListener("resize", relayout);
window.removeEventListener("scroll", relayout, true);
};
}, [goalPanelOpen]);
useEffect(() => {
if (!goalPanelOpen) return;
function onPointerDown(ev: MouseEvent): void {
const target = ev.target as Node | null;
if (!target) return;
if (panelRef.current?.contains(target)) return;
if (expandToggleRef.current?.contains(target)) return;
setGoalPanelOpen(false);
}
function onKey(ev: KeyboardEvent): void {
if (ev.key === "Escape") setGoalPanelOpen(false);
}
window.addEventListener("mousedown", onPointerDown);
window.addEventListener("keydown", onKey);
return () => {
window.removeEventListener("mousedown", onPointerDown);
window.removeEventListener("keydown", onKey);
};
}, [goalPanelOpen]);
const elapsed = const elapsed =
startedAt != null ? Math.max(0, Math.floor(Date.now() / 1000 - startedAt)) : 0; startedAt != null ? Math.max(0, Math.floor(Date.now() / 1000 - startedAt)) : 0;
const m = Math.floor(elapsed / 60); const m = Math.floor(elapsed / 60);
const s = elapsed % 60; const sec = elapsed % 60;
const shortElapsed = m > 0 ? `${m}:${s.toString().padStart(2, "0")}` : `${s}s`; const shortElapsed = m > 0 ? `${m}:${sec.toString().padStart(2, "0")}` : `${sec}s`;
const timerTitle = showTimer const timerTitle = showTimer
? t("thread.composer.runRuntimeTitle", { elapsed: shortElapsed }) ? t("thread.composer.runRuntimeTitle", { elapsed: shortElapsed })
: null; : null;
@ -187,7 +267,52 @@ function RunElapsedStrip({
const ariaLabel = ariaParts.join(" · "); const ariaLabel = ariaParts.join(" · ");
return ( return (
<> <div ref={stripWrapperRef} className="relative z-30">
{goalPanelOpen && canExpandGoal && markdownBody ? (
<div
ref={panelRef}
id="nanobot-goal-panel-root"
role="dialog"
aria-modal="false"
aria-labelledby="nanobot-goal-panel-title"
tabIndex={-1}
className={cn(
"absolute bottom-[calc(100%+8px)] left-3 right-3 z-[50] flex max-w-none flex-col overflow-hidden",
"rounded-2xl border border-black/[0.08] bg-card shadow-[0_12px_40px_rgba(15,23,42,0.14)]",
"backdrop-blur-sm dark:border-white/[0.1] dark:shadow-[0_16px_48px_rgba(0,0,0,0.45)]",
)}
style={{ maxHeight: `${Math.round(panelMaxPx)}px` }}
>
<div className="flex shrink-0 items-center justify-between gap-2 border-b border-black/[0.06] px-3 py-2 dark:border-white/[0.08]">
<h2
id="nanobot-goal-panel-title"
className="min-w-0 truncate text-[13px] font-semibold tracking-tight text-foreground"
>
{t("thread.composer.goalStateSheetTitle")}
</h2>
<button
type="button"
className={cn(
"inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full",
"text-muted-foreground transition-colors hover:bg-muted/65 hover:text-foreground",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
)}
aria-label={t("thread.composer.goalStateCloseAria")}
onClick={() => setGoalPanelOpen(false)}
>
<X className="h-4 w-4" aria-hidden />
</button>
</div>
<div
id="nanobot-goal-panel-scroll"
className="min-h-0 flex-1 overflow-y-auto scrollbar-thin px-3 pb-3 pt-2"
>
<MarkdownText className="max-w-none text-[13.5px] leading-relaxed text-foreground/90">
{markdownBody}
</MarkdownText>
</div>
</div>
) : null}
<div <div
className="flex min-h-[36px] items-center gap-2 border-b border-black/[0.04] px-3 py-2 dark:border-white/[0.06]" className="flex min-h-[36px] items-center gap-2 border-b border-black/[0.04] px-3 py-2 dark:border-white/[0.06]"
role="status" role="status"
@ -213,55 +338,28 @@ function RunElapsedStrip({
</span> </span>
{canExpandGoal ? ( {canExpandGoal ? (
<button <button
ref={expandToggleRef}
type="button" type="button"
className={cn( className={cn(
"inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full", "inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full",
"text-muted-foreground transition-colors hover:bg-muted/55 hover:text-foreground", "text-muted-foreground transition-colors hover:bg-muted/55 hover:text-foreground",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
)} )}
aria-expanded={goalPanelOpen}
aria-controls={goalPanelOpen ? "nanobot-goal-panel-root" : undefined}
aria-label={t("thread.composer.goalStateExpandAria")} aria-label={t("thread.composer.goalStateExpandAria")}
title={t("thread.composer.goalStateExpandAria")} title={t("thread.composer.goalStateExpandAria")}
onClick={() => setGoalSheetOpen(true)} onClick={() => setGoalPanelOpen((o) => !o)}
> >
{goalPanelOpen ? (
<ChevronDown className="h-4 w-4" aria-hidden />
) : (
<ChevronUp className="h-4 w-4" aria-hidden /> <ChevronUp className="h-4 w-4" aria-hidden />
)}
</button> </button>
) : null} ) : null}
</div> </div>
<Sheet open={goalSheetOpen} onOpenChange={setGoalSheetOpen}>
<SheetContent
side="bottom"
showCloseButton
aria-describedby={undefined}
className={cn(
"max-h-[min(85vh,560px)] rounded-t-2xl border-t px-4 pb-6 pt-4",
"gap-3 sm:max-w-lg sm:rounded-t-2xl",
)}
>
<SheetHeader className="space-y-1 text-left">
<SheetTitle>{t("thread.composer.goalStateSheetTitle")}</SheetTitle>
</SheetHeader>
<div className="flex max-h-[min(58vh,420px)] flex-col gap-4 overflow-y-auto pr-0.5 text-[14px] leading-relaxed">
{summaryFull ? (
<section>
<p className="mb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">
{t("thread.composer.goalStateSummaryHeading")}
</p>
<p className="whitespace-pre-wrap text-foreground/90">{summaryFull}</p>
</section>
) : null}
{objectiveFull ? (
<section>
<p className="mb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">
{t("thread.composer.goalStateObjectiveHeading")}
</p>
<p className="whitespace-pre-wrap text-foreground/90">{objectiveFull}</p>
</section>
) : null}
</div> </div>
</SheetContent>
</Sheet>
</>
); );
} }
@ -655,7 +753,7 @@ export function ThreadComposer({
disabled && "opacity-60", disabled && "opacity-60",
isDragging && "ring-2 ring-primary/40 motion-reduce:ring-0 motion-reduce:border-primary", isDragging && "ring-2 ring-primary/40 motion-reduce:ring-0 motion-reduce:border-primary",
goalState?.active && goalState?.active &&
"thread-goal-shell-glow ring-1 ring-sky-400/35 motion-reduce:ring-sky-400/25 dark:ring-sky-400/45", "goal-shell-glow ring-1 ring-sky-400/35 motion-reduce:ring-sky-400/25 dark:ring-sky-400/45",
)} )}
> >
{images.length > 0 ? ( {images.length > 0 ? (

View File

@ -168,7 +168,7 @@
} }
/** Goal halo: pale sky blue (not ``--primary``, which often reads as neutral gray). */ /** Goal halo: pale sky blue (not ``--primary``, which often reads as neutral gray). */
@keyframes thread-goal-glow-breathe { @keyframes goal-shell-glow-breathe {
0%, 0%,
100% { 100% {
filter: drop-shadow(0 0 10px hsl(204 72% 52% / 0.22)) filter: drop-shadow(0 0 10px hsl(204 72% 52% / 0.22))
@ -179,10 +179,10 @@
drop-shadow(0 0 38px hsl(199 85% 55% / 0.2)); drop-shadow(0 0 38px hsl(199 85% 55% / 0.2));
} }
} }
.thread-goal-shell-glow { .goal-shell-glow {
animation: thread-goal-glow-breathe 4.8s ease-in-out infinite; animation: goal-shell-glow-breathe 4.8s ease-in-out infinite;
} }
@keyframes thread-goal-glow-breathe-dark { @keyframes goal-shell-glow-breathe-dark {
0%, 0%,
100% { 100% {
filter: drop-shadow(0 0 12px hsl(198 90% 72% / 0.28)) filter: drop-shadow(0 0 12px hsl(198 90% 72% / 0.28))
@ -193,15 +193,15 @@
drop-shadow(0 0 42px hsl(195 100% 70% / 0.24)); drop-shadow(0 0 42px hsl(195 100% 70% / 0.24));
} }
} }
.dark .thread-goal-shell-glow { .dark .goal-shell-glow {
animation-name: thread-goal-glow-breathe-dark; animation-name: goal-shell-glow-breathe-dark;
} }
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.thread-goal-shell-glow { .goal-shell-glow {
animation: none; animation: none;
filter: drop-shadow(0 0 14px hsl(204 70% 50% / 0.24)); filter: drop-shadow(0 0 14px hsl(204 70% 50% / 0.24));
} }
.dark .thread-goal-shell-glow { .dark .goal-shell-glow {
filter: drop-shadow(0 0 14px hsl(198 88% 70% / 0.32)); filter: drop-shadow(0 0 14px hsl(198 88% 70% / 0.32));
} }
} }

View File

@ -248,9 +248,8 @@
"goalStateStrip": "Goal · {{label}}", "goalStateStrip": "Goal · {{label}}",
"goalStateFallback": "Goal", "goalStateFallback": "Goal",
"goalStateExpandAria": "Show full goal", "goalStateExpandAria": "Show full goal",
"goalStateSheetTitle": "Thread goal", "goalStateSheetTitle": "Goal",
"goalStateSummaryHeading": "Summary", "goalStateCloseAria": "Close goal",
"goalStateObjectiveHeading": "Objective",
"send": "Send message", "send": "Send message",
"stop": "Stop response", "stop": "Stop response",
"attachImage": "Attach image", "attachImage": "Attach image",

View File

@ -222,9 +222,7 @@
"goalStateStrip": "Objetivo · {{label}}", "goalStateStrip": "Objetivo · {{label}}",
"goalStateFallback": "Objetivo", "goalStateFallback": "Objetivo",
"goalStateExpandAria": "Ver objetivo completo", "goalStateExpandAria": "Ver objetivo completo",
"goalStateSheetTitle": "Objetivo del hilo", "goalStateSheetTitle": "Objetivo",
"goalStateSummaryHeading": "Resumen",
"goalStateObjectiveHeading": "Objetivo",
"send": "Enviar mensaje", "send": "Enviar mensaje",
"stop": "Detener respuesta", "stop": "Detener respuesta",
"attachImage": "Adjuntar imagen", "attachImage": "Adjuntar imagen",
@ -302,7 +300,8 @@
"description": "Lista los comandos slash disponibles." "description": "Lista los comandos slash disponibles."
} }
} }
} },
"goalStateCloseAria": "Cerrar objetivo"
}, },
"scrollToBottom": "Desplazarse al final" "scrollToBottom": "Desplazarse al final"
}, },

View File

@ -222,9 +222,7 @@
"goalStateStrip": "Objectif · {{label}}", "goalStateStrip": "Objectif · {{label}}",
"goalStateFallback": "Objectif", "goalStateFallback": "Objectif",
"goalStateExpandAria": "Afficher lobjectif complet", "goalStateExpandAria": "Afficher lobjectif complet",
"goalStateSheetTitle": "Objectif du fil", "goalStateSheetTitle": "Objectif",
"goalStateSummaryHeading": "Résumé",
"goalStateObjectiveHeading": "Objectif",
"send": "Envoyer le message", "send": "Envoyer le message",
"stop": "Arrêter la réponse", "stop": "Arrêter la réponse",
"attachImage": "Joindre une image", "attachImage": "Joindre une image",
@ -302,7 +300,8 @@
"description": "Lister les commandes slash disponibles." "description": "Lister les commandes slash disponibles."
} }
} }
} },
"goalStateCloseAria": "Fermer lobjectif"
}, },
"scrollToBottom": "Faire défiler vers le bas" "scrollToBottom": "Faire défiler vers le bas"
}, },

View File

@ -222,9 +222,7 @@
"goalStateStrip": "Tujuan · {{label}}", "goalStateStrip": "Tujuan · {{label}}",
"goalStateFallback": "Tujuan", "goalStateFallback": "Tujuan",
"goalStateExpandAria": "Lihat tujuan lengkap", "goalStateExpandAria": "Lihat tujuan lengkap",
"goalStateSheetTitle": "Tujuan thread", "goalStateSheetTitle": "Tujuan",
"goalStateSummaryHeading": "Ringkasan",
"goalStateObjectiveHeading": "Tujuan",
"send": "Kirim pesan", "send": "Kirim pesan",
"stop": "Hentikan respons", "stop": "Hentikan respons",
"attachImage": "Lampirkan gambar", "attachImage": "Lampirkan gambar",
@ -302,7 +300,8 @@
"description": "Daftar perintah slash yang tersedia." "description": "Daftar perintah slash yang tersedia."
} }
} }
} },
"goalStateCloseAria": "Tutup tujuan"
}, },
"scrollToBottom": "Gulir ke bawah" "scrollToBottom": "Gulir ke bawah"
}, },

View File

@ -222,9 +222,7 @@
"goalStateStrip": "目標 · {{label}}", "goalStateStrip": "目標 · {{label}}",
"goalStateFallback": "目標", "goalStateFallback": "目標",
"goalStateExpandAria": "目標の全文を表示", "goalStateExpandAria": "目標の全文を表示",
"goalStateSheetTitle": "スレッドの目標", "goalStateSheetTitle": "目標",
"goalStateSummaryHeading": "要約",
"goalStateObjectiveHeading": "目的",
"send": "メッセージを送信", "send": "メッセージを送信",
"stop": "応答を停止", "stop": "応答を停止",
"attachImage": "画像を添付", "attachImage": "画像を添付",
@ -302,7 +300,8 @@
"description": "利用可能なスラッシュコマンドを一覧表示します。" "description": "利用可能なスラッシュコマンドを一覧表示します。"
} }
} }
} },
"goalStateCloseAria": "目標を閉じる"
}, },
"scrollToBottom": "一番下へスクロール" "scrollToBottom": "一番下へスクロール"
}, },

View File

@ -222,9 +222,7 @@
"goalStateStrip": "목표 · {{label}}", "goalStateStrip": "목표 · {{label}}",
"goalStateFallback": "목표", "goalStateFallback": "목표",
"goalStateExpandAria": "전체 목표 보기", "goalStateExpandAria": "전체 목표 보기",
"goalStateSheetTitle": "스레드 목표", "goalStateSheetTitle": "목표",
"goalStateSummaryHeading": "요약",
"goalStateObjectiveHeading": "목표 설명",
"send": "메시지 보내기", "send": "메시지 보내기",
"stop": "응답 중지", "stop": "응답 중지",
"attachImage": "이미지 첨부", "attachImage": "이미지 첨부",
@ -302,7 +300,8 @@
"description": "사용 가능한 슬래시 명령을 나열합니다." "description": "사용 가능한 슬래시 명령을 나열합니다."
} }
} }
} },
"goalStateCloseAria": "목표 닫기"
}, },
"scrollToBottom": "맨 아래로 스크롤" "scrollToBottom": "맨 아래로 스크롤"
}, },

View File

@ -222,9 +222,7 @@
"goalStateStrip": "Mục tiêu · {{label}}", "goalStateStrip": "Mục tiêu · {{label}}",
"goalStateFallback": "Mục tiêu", "goalStateFallback": "Mục tiêu",
"goalStateExpandAria": "Xem đầy đủ mục tiêu", "goalStateExpandAria": "Xem đầy đủ mục tiêu",
"goalStateSheetTitle": "Mục tiêu luồng", "goalStateSheetTitle": "Mục tiêu",
"goalStateSummaryHeading": "Tóm tắt",
"goalStateObjectiveHeading": "Mục tiêu",
"send": "Gửi tin nhắn", "send": "Gửi tin nhắn",
"stop": "Dừng phản hồi", "stop": "Dừng phản hồi",
"attachImage": "Đính kèm ảnh", "attachImage": "Đính kèm ảnh",
@ -302,7 +300,8 @@
"description": "Liệt kê các lệnh slash có sẵn." "description": "Liệt kê các lệnh slash có sẵn."
} }
} }
} },
"goalStateCloseAria": "Đóng mục tiêu"
}, },
"scrollToBottom": "Cuộn xuống cuối" "scrollToBottom": "Cuộn xuống cuối"
}, },

View File

@ -236,9 +236,7 @@
"goalStateStrip": "目标 · {{label}}", "goalStateStrip": "目标 · {{label}}",
"goalStateFallback": "目标", "goalStateFallback": "目标",
"goalStateExpandAria": "查看完整目标", "goalStateExpandAria": "查看完整目标",
"goalStateSheetTitle": "会话目标", "goalStateSheetTitle": "目标",
"goalStateSummaryHeading": "摘要",
"goalStateObjectiveHeading": "目标描述",
"send": "发送消息", "send": "发送消息",
"stop": "停止响应", "stop": "停止响应",
"attachImage": "添加图片", "attachImage": "添加图片",
@ -322,7 +320,8 @@
"decode_failed": "无法解码这张图片", "decode_failed": "无法解码这张图片",
"too_large": "图片太大,请换一张小一点的", "too_large": "图片太大,请换一张小一点的",
"io": "无法读取该文件" "io": "无法读取该文件"
} },
"goalStateCloseAria": "关闭目标"
}, },
"scrollToBottom": "滚动到底部" "scrollToBottom": "滚动到底部"
}, },

View File

@ -222,9 +222,7 @@
"goalStateStrip": "目標 · {{label}}", "goalStateStrip": "目標 · {{label}}",
"goalStateFallback": "目標", "goalStateFallback": "目標",
"goalStateExpandAria": "查看完整目標", "goalStateExpandAria": "查看完整目標",
"goalStateSheetTitle": "對話目標", "goalStateSheetTitle": "目標",
"goalStateSummaryHeading": "摘要",
"goalStateObjectiveHeading": "目標描述",
"send": "送出訊息", "send": "送出訊息",
"stop": "停止回覆", "stop": "停止回覆",
"attachImage": "附加圖片", "attachImage": "附加圖片",
@ -302,7 +300,8 @@
"description": "列出可用的斜線命令。" "description": "列出可用的斜線命令。"
} }
} }
} },
"goalStateCloseAria": "關閉目標"
}, },
"scrollToBottom": "捲動到底部" "scrollToBottom": "捲動到底部"
}, },

View File

@ -107,7 +107,7 @@ describe("ThreadComposer", () => {
vi.useRealTimers(); vi.useRealTimers();
}); });
it("opens a bottom sheet with full thread goal when expand is clicked", async () => { it("opens an upward anchored goal panel with markdown content when expand is clicked", async () => {
const longObjective = const longObjective =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz0123456789GoalTail"; "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz0123456789GoalTail";
render( render(
@ -124,12 +124,10 @@ describe("ThreadComposer", () => {
fireEvent.click(screen.getByRole("button", { name: "Show full goal" })); fireEvent.click(screen.getByRole("button", { name: "Show full goal" }));
const dialog = await screen.findByRole("dialog"); const dialog = await screen.findByRole("dialog", { name: "Goal" });
expect(dialog).toBeInTheDocument(); expect(dialog).toBeInTheDocument();
expect(dialog).toHaveTextContent("Short summary for strip"); expect(dialog).toHaveTextContent("Short summary for strip");
expect(dialog).toHaveTextContent(longObjective); expect(dialog).toHaveTextContent(longObjective);
expect(dialog).toHaveTextContent("Summary");
expect(dialog).toHaveTextContent("Objective");
}); });
it("opens a slash command palette and inserts the selected command", () => { it("opens a slash command palette and inserts the selected command", () => {