diff --git a/src/web/common/components/ContextMenu/ChangeEdgeTypeMenuItem.tsx b/src/web/common/components/ContextMenu/ChangeEdgeTypeMenuItem.tsx
index e1365092..baa38fa3 100644
--- a/src/web/common/components/ContextMenu/ChangeEdgeTypeMenuItem.tsx
+++ b/src/web/common/components/ContextMenu/ChangeEdgeTypeMenuItem.tsx
@@ -5,6 +5,7 @@ import { getSameCategoryEdgeTypes } from "@/common/edge";
import { ContextMenuItem } from "@/web/common/components/ContextMenu/CloseOnClickMenuItem";
import { useSessionUser } from "@/web/common/hooks";
import { changeEdgeType } from "@/web/topic/store/actions";
+import { useIsTableEdge } from "@/web/topic/store/edgeHooks";
import { useUserCanEditTopicData } from "@/web/topic/store/userHooks";
import { Edge } from "@/web/topic/utils/graph";
@@ -17,7 +18,11 @@ export const ChangeEdgeTypeMenuItem = ({ edge, parentMenuOpen }: Props) => {
const { sessionUser } = useSessionUser();
const userCanEditTopicData = useUserCanEditTopicData(sessionUser?.username);
+ const isTableEdge = useIsTableEdge(edge.id);
+
if (!userCanEditTopicData) return <>>;
+ // don't allow modifying edges that are part of the table, because they should always exist as long as their nodes do
+ if (isTableEdge) return <>>;
return (
<>
diff --git a/src/web/common/components/ContextMenu/DeleteEdgeMenuItem.tsx b/src/web/common/components/ContextMenu/DeleteEdgeMenuItem.tsx
index a237b37b..c9d387d3 100644
--- a/src/web/common/components/ContextMenu/DeleteEdgeMenuItem.tsx
+++ b/src/web/common/components/ContextMenu/DeleteEdgeMenuItem.tsx
@@ -2,6 +2,7 @@ import { justificationRelationNames } from "@/common/edge";
import { ContextMenuItem } from "@/web/common/components/ContextMenu/CloseOnClickMenuItem";
import { useSessionUser } from "@/web/common/hooks";
import { deleteEdge } from "@/web/topic/store/createDeleteActions";
+import { useIsTableEdge } from "@/web/topic/store/edgeHooks";
import { useUserCanEditTopicData } from "@/web/topic/store/userHooks";
import { Edge } from "@/web/topic/utils/graph";
@@ -9,9 +10,13 @@ export const DeleteEdgeMenuItem = ({ edge }: { edge: Edge }) => {
const { sessionUser } = useSessionUser();
const userCanEditTopicData = useUserCanEditTopicData(sessionUser?.username);
+ const isTableEdge = useIsTableEdge(edge.id);
+
if (!userCanEditTopicData) return <>>;
// doesn't make sense to delete justification edges because they're a tree not a graph - just delete the nodes
if (justificationRelationNames.includes(edge.label)) return <>>;
+ // don't allow modifying edges that are part of the table, because they should always exist as long as their nodes do
+ if (isTableEdge) return <>>;
return deleteEdge(edge.id)}>Delete edge;
};
diff --git a/src/web/topic/components/TopicWorkspace/WorkspaceToolbar.tsx b/src/web/topic/components/TopicWorkspace/WorkspaceToolbar.tsx
index 406b4761..9889967b 100644
--- a/src/web/topic/components/TopicWorkspace/WorkspaceToolbar.tsx
+++ b/src/web/topic/components/TopicWorkspace/WorkspaceToolbar.tsx
@@ -19,6 +19,7 @@ import { useSessionUser } from "@/web/common/hooks";
import { HelpMenu } from "@/web/topic/components/TopicWorkspace/HelpMenu";
import { MoreActionsDrawer } from "@/web/topic/components/TopicWorkspace/MoreActionsDrawer";
import { deleteGraphPart } from "@/web/topic/store/createDeleteActions";
+import { useIsTableEdge } from "@/web/topic/store/edgeHooks";
import { useOnPlayground } from "@/web/topic/store/topicHooks";
import { useUserCanEditTopicData } from "@/web/topic/store/userHooks";
import { redo, undo } from "@/web/topic/store/utilActions";
@@ -44,15 +45,18 @@ import {
export const WorkspaceToolbar = () => {
const { sessionUser } = useSessionUser();
const userCanEditTopicData = useUserCanEditTopicData(sessionUser?.username);
+
const onPlayground = useOnPlayground();
const [canUndo, canRedo] = useTemporalHooks();
const [canGoBack, canGoForward] = useCanGoBackForward();
+
const isComparingPerspectives = useIsComparingPerspectives();
const flashlightMode = useFlashlightMode();
const readonlyMode = useReadonlyMode();
const [hasErrored, setHasErrored] = useState(false);
const selectedGraphPart = useSelectedGraphPart();
+ const partIsTableEdge = useIsTableEdge(selectedGraphPart?.id ?? "");
const [isMoreActionsDrawerOpen, setIsMoreActionsDrawerOpen] = useState(false);
const [helpAnchorEl, setHelpAnchorEl] = useState(null);
@@ -124,7 +128,8 @@ export const WorkspaceToolbar = () => {
deleteGraphPart(selectedGraphPart);
}
}}
- disabled={!selectedGraphPart}
+ // don't allow modifying edges that are part of the table, because they should always exist as long as their nodes do
+ disabled={!selectedGraphPart || partIsTableEdge}
className="hidden sm:flex"
>
diff --git a/src/web/topic/store/edgeHooks.ts b/src/web/topic/store/edgeHooks.ts
index 0511472b..2fd886c8 100644
--- a/src/web/topic/store/edgeHooks.ts
+++ b/src/web/topic/store/edgeHooks.ts
@@ -17,3 +17,17 @@ export const useIsNodeSelected = (edgeId: string) => {
return useIsAnyGraphPartSelected(neighborNodes.map((node) => node.id));
};
+
+export const useIsTableEdge = (edgeId: string) => {
+ return useTopicStore((state) => {
+ try {
+ const edge = findEdgeOrThrow(edgeId, state.edges);
+ if (edge.label !== "fulfills") return false;
+
+ const [parentNode, childNode] = nodes(edge, state.nodes);
+ return parentNode.type === "criterion" && childNode.type === "solution";
+ } catch {
+ return false;
+ }
+ });
+};
diff --git a/src/web/topic/utils/edge.ts b/src/web/topic/utils/edge.ts
index 340e8f5f..9b080b27 100644
--- a/src/web/topic/utils/edge.ts
+++ b/src/web/topic/utils/edge.ts
@@ -246,7 +246,7 @@ export const childNode = (edge: Edge, nodes: Node[]) => {
return findNodeOrThrow(edge.target, nodes);
};
-export const nodes = (edge: Edge, nodes: Node[]) => {
+export const nodes = (edge: Edge, nodes: Node[]): [Node, Node] => {
return [parentNode(edge, nodes), childNode(edge, nodes)];
};