feat(teams): show the full task UUID in the task detail dialog with one-click copy (#1560)

The short identifier (T-015-cc8e) carries only the last four hex chars
of the UUID, while every agent-facing surface — the team_tasks tool
(get / retry / cancel / comment) and the teams.tasks.* RPCs — takes the
full UUID. A human looking at the dashboard therefore had no way to name
a task to the lead in chat. Show the UUID next to the identifier and
status badges, copy it to the clipboard on click, with a short "copied"
confirmation.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
yatulandClaude Fable 5.1 authored and GitHub committed 2026-09-09 00:00:47 +07:00
1 parent 0fef6ff596
commit d98ea89e3b
7 files changed
+58 -6

No files matched your search

+5
View File
@@ -16,6 +16,11 @@ All notable changes to GoClaw are documented here. For full documentation, see [
### Added
- **Task detail dialog shows the full task UUID with one-click copy** — the
short identifier (`T-015-cc8e`) carries only the last four hex characters of
the UUID, while the agent-facing `team_tasks` tool and RPCs take the full
UUID, so a human had no way to name a task to the lead from the dashboard.
- **Behavior UX sidecar delivery overrides** — Adds sidecar-generated Quick
Acknowledgement and Intermediate Replies with provider/model, timeout, token,
and char caps. Effective config resolves Channel > Agent > Workspace, with
+3 -1
View File
@@ -117,7 +117,9 @@
"followupCountMax": "Reminders sent: {{count}}/{{max}}",
"followupNext": "Next reminder:",
"followupDone": "All reminders sent",
"download": "Download"
"download": "Download",
"copyId": "Copy task ID",
"copied": "Copied"
},
"delete": "Delete",
"deleteConfirm": "Delete this task? This cannot be undone.",
+3 -1
View File
@@ -117,7 +117,9 @@
"followupCountMax": "알림 발송: {{count}}/{{max}}회",
"followupNext": "다음 알림:",
"followupDone": "모든 알림 발송됨",
"download": "다운로드"
"download": "다운로드",
"copyId": "작업 ID 복사",
"copied": "복사됨"
},
"delete": "삭제",
"deleteConfirm": "이 태스크를 삭제하시겠습니까? 취소할 수 없습니다.",
+3 -1
View File
@@ -117,7 +117,9 @@
"followupCountMax": "Отправлено напоминаний: {{count}}/{{max}}",
"followupNext": "Следующее напоминание:",
"followupDone": "Все напоминания отправлены",
"download": "Скачать"
"download": "Скачать",
"copyId": "Скопировать ID задачи",
"copied": "Скопировано"
},
"delete": "Удалить",
"deleteConfirm": "Удалить эту задачу? Это действие нельзя отменить.",
+3 -1
View File
@@ -117,7 +117,9 @@
"followupCountMax": "Đã nhắc: {{count}}/{{max}} lần",
"followupNext": "Nhắc tiếp:",
"followupDone": "Đã nhắc hết",
"download": "Tải xuống"
"download": "Tải xuống",
"copyId": "Sao chép ID tác vụ",
"copied": "Đã sao chép"
},
"delete": "Xoá",
"deleteConfirm": "Xoá task này? Không thể hoàn tác.",
+3 -1
View File
@@ -117,7 +117,9 @@
"followupCountMax": "已提醒:{{count}}/{{max}} 次",
"followupNext": "下次提醒:",
"followupDone": "已全部提醒",
"download": "下载"
"download": "下载",
"copyId": "复制任务 ID",
"copied": "已复制"
},
"delete": "删除",
"deleteConfirm": "删除此任务?此操作无法撤消。",
@@ -7,7 +7,7 @@ import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { ConfirmDialog } from "@/components/shared/confirm-dialog";
import {
Trash2, ArrowUp, ArrowDown, ArrowRight, AlertTriangle,
Trash2, ArrowUp, ArrowDown, ArrowRight, AlertTriangle, Copy, Check,
} from "lucide-react";
import { useTranslation } from "react-i18next";
import { formatDate } from "@/lib/format";
@@ -41,6 +41,42 @@ function priorityLabel(priority?: number) {
return priority != null ? (PRIORITY_CONFIG[priority]?.label ?? String(priority)) : "\u2014";
}
/* ── Task UUID with copy ──────────────────────────────────────── */
// The short identifier (T-015-cc8e) carries only the last four hex chars of
// the UUID, while every agent-facing tool (team_tasks get/retry/cancel, comments)
// takes the full UUID. Show it in full and make it one click to copy so a
// human can hand the exact task to the lead in chat.
function TaskIdCopy({ id, label, copiedLabel }: { id: string; label: string; copiedLabel: string }) {
const [copied, setCopied] = useState(false);
useEffect(() => {
if (!copied) return;
const timer = setTimeout(() => setCopied(false), 1500);
return () => clearTimeout(timer);
}, [copied]);
const copy = async () => {
try {
await navigator.clipboard.writeText(id);
setCopied(true);
} catch { /* clipboard unavailable (insecure context) — the text is still selectable */ }
};
return (
<button
type="button"
onClick={copy}
title={copied ? copiedLabel : label}
className="inline-flex items-center gap-1 rounded px-1 font-mono text-[11px] text-muted-foreground hover:bg-accent hover:text-foreground select-all"
>
<span className="truncate max-w-[16rem] sm:max-w-none">{id}</span>
{copied
? <Check className="h-3 w-3 shrink-0 text-green-600" />
: <Copy className="h-3 w-3 shrink-0" />}
</button>
);
}
/* ── Metadata item ────────────────────────────────────────────── */
function MetaItem({ label, children }: { label: string; children: React.ReactNode }) {
@@ -129,6 +165,7 @@ export function TaskDetailDialog({
<Badge variant={taskStatusBadgeVariant(task.status)} className="text-xs">
{task.status.replace(/_/g, " ")}
</Badge>
<TaskIdCopy id={task.id} label={t("tasks.detail.copyId")} copiedLabel={t("tasks.detail.copied")} />
</div>
<DialogTitle className="text-base sm:text-lg">{task.subject}</DialogTitle>
</DialogHeader>