From b040165f4e65489d60ab360de2f4929298a2da46 Mon Sep 17 00:00:00 2001 From: Naiyuan Qing <145280634+NevilleQingNY@users.noreply.github.com> Date: Wed, 20 May 2026 16:43:58 +0800 Subject: [PATCH] feat(squads): skeleton loader + AlertDialog archive confirm (MUL-2437) (#2890) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(squads): skeleton loader + AlertDialog archive confirm (MUL-2437) - Replace `Loading...` text on the squads list with a Skeleton placeholder matching the SquadCard shape (avatar + title + subtitle), aligning with the Agents / Dashboard pattern. - Replace the native `confirm()` on the squad detail Archive button with the project's AlertDialog (destructive variant, pending-disabled, i18n copy interpolating the squad name). Co-Authored-By: Claude Opus 4.7 Co-authored-by: multica-agent * fix(squads): drop misleading restore copy from archive confirm (MUL-2437) Archive is irreversible — there is no unarchive command (see apps/docs/content/docs/squads.mdx:113). Aligns dialog copy with docs: tells the user the action can't be undone and to create a new squad if they need the routing back. Co-authored-by: multica-agent --------- Co-authored-by: Claude Opus 4.7 Co-authored-by: multica-agent --- packages/views/locales/en/squads.json | 7 ++++ packages/views/locales/zh-Hans/squads.json | 7 ++++ .../squads/components/squad-detail-page.tsx | 33 ++++++++++++++++++- .../views/squads/components/squads-page.tsx | 27 ++++++++++++++- 4 files changed, 72 insertions(+), 2 deletions(-) diff --git a/packages/views/locales/en/squads.json b/packages/views/locales/en/squads.json index 6ce069627..e8c67a48b 100644 --- a/packages/views/locales/en/squads.json +++ b/packages/views/locales/en/squads.json @@ -9,6 +9,13 @@ "details_section": "Details", "archive_button": "Archive" }, + "archive_dialog": { + "title": "Archive this squad?", + "description": "\"{{name}}\" will be archived. Issues currently assigned to this squad will be transferred to its leader. This can't be undone — create a new squad if you need the routing back.", + "cancel": "Cancel", + "confirm": "Archive", + "archiving": "Archiving…" + }, "name_editor": { "cancel": "Cancel" }, diff --git a/packages/views/locales/zh-Hans/squads.json b/packages/views/locales/zh-Hans/squads.json index c4b0c0c4c..33db9e629 100644 --- a/packages/views/locales/zh-Hans/squads.json +++ b/packages/views/locales/zh-Hans/squads.json @@ -9,6 +9,13 @@ "details_section": "详情", "archive_button": "归档" }, + "archive_dialog": { + "title": "归档这个小队?", + "description": "“{{name}}” 将被归档,该小队当前承接的 issue 会转交给小队负责人。此操作无法撤销,如需恢复路由请新建小队。", + "cancel": "取消", + "confirm": "归档", + "archiving": "归档中…" + }, "name_editor": { "cancel": "取消" }, diff --git a/packages/views/squads/components/squad-detail-page.tsx b/packages/views/squads/components/squad-detail-page.tsx index 8445c85f0..743daa2d6 100644 --- a/packages/views/squads/components/squad-detail-page.tsx +++ b/packages/views/squads/components/squad-detail-page.tsx @@ -116,6 +116,7 @@ export function SquadDetailPage() { const [showAddMember, setShowAddMember] = useState(false); const [showCreateAgent, setShowCreateAgent] = useState(false); + const [confirmArchive, setConfirmArchive] = useState(false); const updateSquadMut = useMutation({ mutationFn: (data: { name?: string; description?: string; instructions?: string; avatar_url?: string; leader_id?: string }) => api.updateSquad(squadId, data), @@ -225,7 +226,7 @@ export function SquadDetailPage() {

{squad.name}

- @@ -288,6 +289,36 @@ export function SquadDetailPage() { onCreate={handleCreateAgent} /> )} + + {confirmArchive && ( + { if (!v && !deleteMut.isPending) setConfirmArchive(false); }} + > + + + {t(($) => $.archive_dialog.title)} + + {t(($) => $.archive_dialog.description, { name: squad.name })} + + + + + {t(($) => $.archive_dialog.cancel)} + + deleteMut.mutate()} + disabled={deleteMut.isPending} + className="bg-destructive text-white hover:bg-destructive/90" + > + {deleteMut.isPending + ? t(($) => $.archive_dialog.archiving) + : t(($) => $.archive_dialog.confirm)} + + + + + )} ); } diff --git a/packages/views/squads/components/squads-page.tsx b/packages/views/squads/components/squads-page.tsx index 0e2e0b4ea..d221dd184 100644 --- a/packages/views/squads/components/squads-page.tsx +++ b/packages/views/squads/components/squads-page.tsx @@ -11,6 +11,7 @@ import { PageHeader } from "../../layout/page-header"; import { Users, Plus, Search, Bot, User } from "lucide-react"; import { Button } from "@multica/ui/components/ui/button"; import { Input } from "@multica/ui/components/ui/input"; +import { Skeleton } from "@multica/ui/components/ui/skeleton"; import { ActorAvatar as ActorAvatarBase } from "@multica/ui/components/common/actor-avatar"; import { useModalStore } from "@multica/core/modals"; import type { Agent, Squad } from "@multica/core/types"; @@ -83,7 +84,7 @@ export function SquadsPage() {
{isLoading ? ( -
Loading...
+ ) : squads.length === 0 ? (
@@ -184,6 +185,30 @@ function ScopeButton({ active, label, count, onClick }: { active: boolean; label ); } +function SquadsListSkeleton() { + return ( + <> +
+ + +
+
+
+ {Array.from({ length: 4 }).map((_, i) => ( +
+ +
+ + +
+
+ ))} +
+
+ + ); +} + function SquadAvatar({ squad }: { squad: Squad }) { const initials = squad.name .split(" ")