diff --git a/packages/views/settings/components/delete-workspace-dialog.test.tsx b/packages/views/settings/components/delete-workspace-dialog.test.tsx index dcd033aebf..c19144b585 100644 --- a/packages/views/settings/components/delete-workspace-dialog.test.tsx +++ b/packages/views/settings/components/delete-workspace-dialog.test.tsx @@ -126,6 +126,48 @@ describe("DeleteWorkspaceDialog", () => { expect(screen.getByRole("button", { name: "Cancel" })).toBeDisabled(); }); + it("matches names with spaces, unicode, and other non-ASCII characters literally", async () => { + const user = userEvent.setup(); + const onConfirm = vi.fn(); + render( + , + ); + const input = screen.getByRole("textbox"); + await user.type(input, "My 团队 🚀"); + expect(screen.getByRole("button", { name: "Delete workspace" })).toBeEnabled(); + await user.click(screen.getByRole("button", { name: "Delete workspace" })); + expect(onConfirm).toHaveBeenCalledTimes(1); + }); + + it("resets the input when the workspace being deleted changes (e.g. rename mid-dialog)", () => { + const { rerender } = render( + , + ); + const input = screen.getByRole("textbox") as HTMLInputElement; + // Simulate user typing (set value directly since userEvent.type would + // lose focus across re-renders). + input.value = "old-name"; + rerender( + , + ); + expect(screen.getByRole("textbox")).toHaveValue(""); + }); + it("clears the input when reopened so prior attempts don't leak", async () => { const user = userEvent.setup(); const { rerender } = render( diff --git a/packages/views/settings/components/delete-workspace-dialog.tsx b/packages/views/settings/components/delete-workspace-dialog.tsx index 4d1e24509f..64e0240d68 100644 --- a/packages/views/settings/components/delete-workspace-dialog.tsx +++ b/packages/views/settings/components/delete-workspace-dialog.tsx @@ -45,9 +45,13 @@ export function DeleteWorkspaceDialog({ const [typed, setTyped] = useState(""); const matched = typed === workspaceName; + // Reset on close (so reopening for a different workspace doesn't leak + // the prior attempt) AND on workspaceName change (if another owner + // renames the workspace while the dialog is open, the already-typed + // string stops matching and there'd be no feedback explaining why). useEffect(() => { - if (!open) setTyped(""); - }, [open]); + setTyped(""); + }, [open, workspaceName]); const submit = () => { if (!matched || loading) return; diff --git a/packages/views/settings/components/workspace-tab.tsx b/packages/views/settings/components/workspace-tab.tsx index f4e6a4fb50..29ddf82a77 100644 --- a/packages/views/settings/components/workspace-tab.tsx +++ b/packages/views/settings/components/workspace-tab.tsx @@ -38,7 +38,7 @@ export function WorkspaceTab() { const user = useAuthStore((s) => s.user); const workspace = useCurrentWorkspace(); const wsId = useWorkspaceId(); - const { data: members = [] } = useQuery(memberListOptions(wsId)); + const { data: members = [], isFetched: membersFetched } = useQuery(memberListOptions(wsId)); const qc = useQueryClient(); const leaveWorkspace = useLeaveWorkspace(); const deleteWorkspace = useDeleteWorkspace(); @@ -214,7 +214,10 @@ export function WorkspaceTab() { - {/* Danger Zone */} + {/* Danger Zone — gated on the member query settling so the owner-only + Delete button and the sole-owner Leave guidance don't flash in + after mount. */} + {membersFetched && (
@@ -265,6 +268,7 @@ export function WorkspaceTab() {
+ )} { if (!v) setConfirmAction(null); }}>