feat: remount the automation and sequence canvases on param-only navigation and restrict jump-to-teammate to distinct in-app paths so a jump can never save one record's graph into another

This commit is contained in:
Matthew Meszaros
2026-07-02 20:33:44 +02:00
parent f804538ff5
commit 226e83683c
3 changed files with 16 additions and 5 deletions
@@ -36,7 +36,12 @@ export default function AutomationBuilderPage() {
}
return (
// Keyed by id: a param-only navigation (e.g. jump-to-teammate from one
// automation to another) must remount the builder, not hand a seeded
// canvas a different automation — Save would write the old graph into
// the new record.
<AutomationFlow
key={autoQ.data.automation.id}
automation={autoQ.data.automation}
connections={connQ.data?.connections ?? []}
catalog={catQ.data?.catalog ?? []}
@@ -71,7 +71,10 @@ function StepsBuilder({ campaignId }: { campaignId: string }) {
);
}
return <CampaignFlow campaignId={campaignId} />;
// Keyed by campaign: a param-only navigation (e.g. jump-to-teammate from
// one campaign's steps to another's) must remount the canvas, never reuse
// one seeded from the previous campaign.
return <CampaignFlow key={campaignId} campaignId={campaignId} />;
}
function StepsSkeleton() {
@@ -1,5 +1,5 @@
import { useRef, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useLocation, useNavigate } from "react-router-dom";
import { AnimatePresence, motion } from "framer-motion";
import { ArrowRightIcon, EyeIcon, PencilIcon, ReplyIcon } from "lucide-react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
@@ -75,6 +75,7 @@ export default function PresenceAvatars() {
const [open, setOpen] = useState(false);
const ref = useRef<HTMLDivElement>(null);
const navigate = useNavigate();
const { pathname } = useLocation();
useClickOutside(ref, () => setOpen(false));
if (members.length === 0) return null;
@@ -147,7 +148,9 @@ export default function PresenceAvatars() {
{members.map((m) => {
const act = activityOf(m);
// Click a teammate to jump to the page they are on.
const jumpable = !!m.page;
// Presence pages are teammate-supplied strings, so
// only in-app paths are navigable.
const jumpable = !!m.page && m.page.startsWith("/app");
return (
<button
key={m.userId}
@@ -155,9 +158,9 @@ export default function PresenceAvatars() {
disabled={!jumpable}
title={jumpable ? "Go where they are" : undefined}
onClick={() => {
if (!m.page) return;
setOpen(false);
navigate(m.page);
if (!jumpable || m.page === pathname) return;
navigate(m.page!);
}}
className={cn(
"group w-full px-3 py-1.5 flex items-center gap-2.5 text-left",