feat(frontend): rename group (reassign members via setSessionGroup)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-29 21:32:32 +02:00
co-authored by Claude Sonnet 4.6
parent 5442a07ced
commit 84db1e1085
2 changed files with 60 additions and 9 deletions
+41 -9
View File
@@ -9,6 +9,7 @@ import { RenameDialog } from "./RenameDialog";
import { DeleteConfirmDialog } from "./DeleteConfirmDialog";
import { SteerInput, ComposerFooter } from "./SteerInput";
import { WorkflowBar } from "./WorkflowBar";
import { Pencil } from "lucide-react";
import { Button } from "../components/ui/button";
import { Toaster } from "../components/ui/sonner";
import { toast } from "sonner";
@@ -40,6 +41,7 @@ export function AppShell() {
const [renameTarget, setRenameTarget] = useState<SessionSummary | null>(null);
const [deleteTarget, setDeleteTarget] = useState<SessionSummary | null>(null);
const [collapsedGroups, setCollapsedGroups] = useState<Record<string, boolean>>({});
const [renameGroupTarget, setRenameGroupTarget] = useState<string | null>(null);
const groups = useMemo(
() => [...new Set(sessions.map((s) => s.group).filter((g): g is string => !!g))].sort(),
@@ -79,6 +81,18 @@ export function AppShell() {
}
}
}
async function renameGroup(oldName: string, newName: string) {
const trimmed = newName.trim();
if (!trimmed || trimmed === oldName) return;
try {
for (const s of sessions.filter((x) => x.group === oldName)) {
await setSessionGroup(s.id, trimmed);
}
refresh();
} catch {
toast.error("Failed to rename group.");
}
}
async function toggleArchive(s: SessionSummary) {
try {
await (s.archived ? unarchiveSession(s.id) : archiveSession(s.id));
@@ -225,15 +239,25 @@ export function AppShell() {
<div className="flex flex-col gap-3 pb-2">
{groups.map((g) => (
<div key={g}>
<button
type="button"
onClick={() => setCollapsedGroups((c) => ({ ...c, [g]: !c[g] }))}
aria-expanded={!collapsedGroups[g]}
className="flex w-full items-center gap-1 px-1 pb-1 text-left text-[0.7rem] font-semibold uppercase tracking-wide text-muted-foreground hover:text-foreground"
>
<span className="select-none">{collapsedGroups[g] ? "▸" : "▾"}</span>
<span className="truncate">{g}</span>
</button>
<div className="group/gh flex items-center gap-1">
<button
type="button"
onClick={() => setCollapsedGroups((c) => ({ ...c, [g]: !c[g] }))}
aria-expanded={!collapsedGroups[g]}
className="flex min-w-0 flex-1 items-center gap-1 px-1 pb-1 text-left text-[0.7rem] font-semibold uppercase tracking-wide text-muted-foreground hover:text-foreground"
>
<span className="select-none">{collapsedGroups[g] ? "▸" : "▾"}</span>
<span className="truncate">{g}</span>
</button>
<button
type="button"
aria-label={`Rename group ${g}`}
onClick={() => setRenameGroupTarget(g)}
className="rounded p-0.5 text-muted-foreground opacity-0 transition-opacity hover:bg-accent group-hover/gh:opacity-100"
>
<Pencil className="size-3" />
</button>
</div>
{!collapsedGroups[g] && (
<NavSessions
sessions={activeList.filter((s) => s.group === g)}
@@ -291,6 +315,14 @@ export function AppShell() {
}}
/>
)}
{renameGroupTarget && (
<RenameDialog
open
initial={renameGroupTarget}
onOpenChange={(o) => { if (!o) setRenameGroupTarget(null); }}
onSubmit={async (name) => { await renameGroup(renameGroupTarget, name); setRenameGroupTarget(null); }}
/>
)}
{deleteTarget && (
<DeleteConfirmDialog
open