Skip to content

Commit 895543d

Browse files
lucbrinkmanclaude
andcommitted
Merge main into probability-bug-fix
Resolved conflict in lib/probability.ts: - Keep renamed EdgeType.ALWAYS from main - Keep new Map-based lookup logic from bug fix 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
2 parents d509c18 + cb45ff5 commit 895543d

File tree

14 files changed

+299
-203
lines changed

14 files changed

+299
-203
lines changed

app/page.tsx

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { WelcomeModal } from '@/components/WelcomeModal';
1313
import { AuthModal } from '@/components/auth/AuthModal';
1414
import MobileWarning from '@/components/MobileWarning';
1515
import { useAuth } from '@/hooks/useAuth';
16-
import { MIN_ZOOM, MAX_ZOOM, ZOOM_STEP, type GraphData, type DocumentData, type GraphNode } from '@/lib/types';
16+
import { MIN_ZOOM, MAX_ZOOM, ZOOM_STEP, NodeType, EdgeType, type GraphData, type DocumentData, type GraphNode } from '@/lib/types';
1717
import { startNodeIndex, AUTHORS_ESTIMATES, graphData as defaultGraphData } from '@/lib/graphData';
1818
import { calculateProbabilities } from '@/lib/probability';
1919
import { loadFromLocalStorage, saveToLocalStorage, createDefaultDocumentData, clearLocalStorage, createEmptyDocumentData } from '@/lib/documentState';
@@ -157,7 +157,7 @@ function HomeContent() {
157157
if (node.probability === undefined) {
158158
return {
159159
...node,
160-
probability: node.type === 'n' ? 50 : null
160+
probability: node.type === NodeType.QUESTION ? 50 : null
161161
};
162162
}
163163
return node;
@@ -191,7 +191,7 @@ function HomeContent() {
191191
if (node.probability === undefined) {
192192
return {
193193
...node,
194-
probability: node.type === 'n' ? 50 : null
194+
probability: node.type === NodeType.QUESTION ? 50 : null
195195
};
196196
}
197197
return node;
@@ -250,7 +250,7 @@ function HomeContent() {
250250

251251
// Find max probability among outcome nodes (good, ambivalent, existential)
252252
const outcomeNodes = result.nodes.filter(
253-
n => n.type === 'g' || n.type === 'a' || n.type === 'e'
253+
n => n.type === NodeType.GOOD || n.type === NodeType.AMBIVALENT || n.type === NodeType.EXISTENTIAL
254254
);
255255
const maxOutcomeProbability = Math.max(
256256
...outcomeNodes.map(n => n.p),
@@ -510,16 +510,16 @@ function HomeContent() {
510510

511511
if (updatedConnections.length === 0) {
512512
// No connections left: convert to AMBIVALENT outcome
513-
updatedType = 'a';
513+
updatedType = NodeType.AMBIVALENT;
514514
updatedSliderIndex = null; // Clear sliderIndex
515515
} else if (updatedConnections.length === 1) {
516516
// One connection left: convert from QUESTION to INTERMEDIATE
517517
// Also convert the remaining connection from YES/NO to E100
518-
updatedType = 'i';
518+
updatedType = NodeType.INTERMEDIATE;
519519
updatedSliderIndex = null; // Clear sliderIndex
520520
finalConnections = updatedConnections.map(conn => ({
521521
...conn,
522-
type: '-' as const,
522+
type: EdgeType.ALWAYS,
523523
label: '',
524524
}));
525525
}
@@ -532,7 +532,7 @@ function HomeContent() {
532532
// Re-index remaining question nodes if we removed a question
533533
if (sliderIndexToRemove !== null && sliderIndexToRemove !== undefined) {
534534
updatedNodes = updatedNodes.map(n => {
535-
if (n.type === 'n' && n.sliderIndex !== null && n.sliderIndex > sliderIndexToRemove) {
535+
if (n.type === NodeType.QUESTION && n.sliderIndex !== null && n.sliderIndex > sliderIndexToRemove) {
536536
return { ...n, sliderIndex: n.sliderIndex - 1 };
537537
}
538538
return n;
@@ -563,11 +563,11 @@ function HomeContent() {
563563
const newEdgeIndex = currentConnectionCount; // Will be added at this index
564564

565565
// Determine if this will convert to a question node
566-
const isIntermediateNode = targetNode.type === 'i';
566+
const isIntermediateNode = targetNode.type === NodeType.INTERMEDIATE;
567567
const willBecomeQuestion = isIntermediateNode && targetNode.connections.length === 1;
568568

569569
// Calculate the next sliderIndex BEFORE state updates (for new question nodes)
570-
const existingQuestions = graphData.nodes.filter(n => n.type === 'n');
570+
const existingQuestions = graphData.nodes.filter(n => n.type === NodeType.QUESTION);
571571
const maxSliderIndex = existingQuestions.reduce(
572572
(max, n) => n.sliderIndex !== null && n.sliderIndex > max ? n.sliderIndex : max,
573573
-1
@@ -613,32 +613,32 @@ function HomeContent() {
613613
}
614614
}
615615

616-
const isOutcomeNode = node.type === 'g' || node.type === 'a' || node.type === 'e';
617-
const isIntermediateNode = node.type === 'i';
616+
const isOutcomeNode = node.type === NodeType.GOOD || node.type === NodeType.AMBIVALENT || node.type === NodeType.EXISTENTIAL;
617+
const isIntermediateNode = node.type === NodeType.INTERMEDIATE;
618618

619619
if (isOutcomeNode && node.connections.length === 0) {
620620
// Case: OUTCOME node with 0 connections -> add 1 E100 connection -> convert to INTERMEDIATE
621621
const newConnection = {
622-
type: '-' as const,
622+
type: EdgeType.ALWAYS,
623623
targetX,
624624
targetY,
625625
label: '',
626626
};
627627

628-
return { ...node, connections: [newConnection], type: 'i' as const };
628+
return { ...node, connections: [newConnection], type: NodeType.INTERMEDIATE };
629629
} else if (isIntermediateNode && node.connections.length === 1) {
630630
// Case: INTERMEDIATE node with 1 connection -> add YES/NO connections -> convert to QUESTION
631631
// Convert existing connection from E100 to YES
632632
const updatedExistingConnections = node.connections.map(conn => {
633-
if (conn.type === '-') {
634-
return { ...conn, type: 'y' as const, label: 'Yes' };
633+
if (conn.type === EdgeType.ALWAYS) {
634+
return { ...conn, type: EdgeType.YES, label: 'Yes' };
635635
}
636636
return conn;
637637
});
638638

639639
// Add new connection with NO type
640640
const newConnection = {
641-
type: 'n' as const,
641+
type: EdgeType.NO,
642642
targetX,
643643
targetY,
644644
label: 'No',
@@ -648,7 +648,7 @@ function HomeContent() {
648648

649649
// Convert node from INTERMEDIATE to QUESTION
650650
// CRITICAL: Assign sliderIndex and initialize probability
651-
return { ...node, connections: updatedConnections, type: 'n' as const, sliderIndex: newSliderIndex, probability: 50 };
651+
return { ...node, connections: updatedConnections, type: NodeType.QUESTION, sliderIndex: newSliderIndex, probability: 50 };
652652
}
653653
}
654654
return node;
@@ -776,7 +776,7 @@ function HomeContent() {
776776

777777
setGraphData(prev => {
778778
const updatedNodes = prev.nodes.map(node => {
779-
if (node.type === 'n' && node.sliderIndex !== null) {
779+
if (node.type === NodeType.QUESTION && node.sliderIndex !== null) {
780780
return { ...node, probability: 50 };
781781
}
782782
return node;
@@ -798,7 +798,7 @@ function HomeContent() {
798798
// Only update probability for question nodes that exist in the default data
799799
// AND have a valid probability value (not null or undefined)
800800
// Custom nodes and nodes without default probabilities are left unchanged
801-
if (node.type === 'n' && node.sliderIndex !== null && defaultNode && defaultNode.probability != null) {
801+
if (node.type === NodeType.QUESTION && node.sliderIndex !== null && defaultNode && defaultNode.probability != null) {
802802
// Preserve all node fields (including custom title), only update probability
803803
return { ...node, probability: defaultNode.probability };
804804
}
@@ -1004,7 +1004,7 @@ function HomeContent() {
10041004
if (node.probability === undefined) {
10051005
return {
10061006
...node,
1007-
probability: node.type === 'n' ? 50 : null
1007+
probability: node.type === NodeType.QUESTION ? 50 : null
10081008
};
10091009
}
10101010
return node;
@@ -1099,7 +1099,7 @@ function HomeContent() {
10991099
// Create a new ambivalent outcome node at the clicked position
11001100
const newNode = {
11011101
id: newNodeId,
1102-
type: 'a' as const, // Ambivalent outcome node type
1102+
type: NodeType.AMBIVALENT, // Ambivalent outcome node type
11031103
title: '',
11041104
connections: [], // Outcome nodes have no outgoing connections
11051105
position: { x, y },
@@ -1136,7 +1136,7 @@ function HomeContent() {
11361136
// Create a new ambivalent outcome node at the specified position
11371137
const newNode = {
11381138
id: newNodeId,
1139-
type: 'a' as const, // Ambivalent outcome node type
1139+
type: NodeType.AMBIVALENT, // Ambivalent outcome node type
11401140
title: '',
11411141
connections: [], // Outcome nodes have no outgoing connections
11421142
position: { x: position.x, y: position.y },
@@ -1257,7 +1257,7 @@ function HomeContent() {
12571257
if (!node) return;
12581258

12591259
// Prevent deleting the start node
1260-
if (node.type === 's') {
1260+
if (node.type === NodeType.START) {
12611261
alert('Cannot delete the start node');
12621262
return;
12631263
}
@@ -1272,12 +1272,12 @@ function HomeContent() {
12721272
}, [handleDeleteEdge]);
12731273

12741274
// Change node type handler
1275-
const handleChangeNodeType = useCallback((nodeId: string, newType: 'n' | 'i' | 'g' | 'a' | 'e') => {
1275+
const handleChangeNodeType = useCallback((nodeId: string, newType: NodeType) => {
12761276
const node = graphData.nodes.find(n => n.id === nodeId);
12771277
if (!node) return;
12781278

12791279
// Don't allow changing start node type
1280-
if (node.type === 's') {
1280+
if (node.type === NodeType.START) {
12811281
alert('Cannot change the type of the start node');
12821282
return;
12831283
}
@@ -1291,9 +1291,9 @@ function HomeContent() {
12911291
if (n.id !== nodeId) return n;
12921292

12931293
// Changing TO question node
1294-
if (newType === 'n') {
1294+
if (newType === NodeType.QUESTION) {
12951295
// Find the highest sliderIndex among existing questions
1296-
const questionNodes = prev.nodes.filter(node => node.type === 'n');
1296+
const questionNodes = prev.nodes.filter(node => node.type === NodeType.QUESTION);
12971297
const maxSliderIndex = questionNodes.reduce((max, node) =>
12981298
node.sliderIndex !== null && node.sliderIndex > max ? node.sliderIndex : max, -1);
12991299
const newSliderIndex = maxSliderIndex + 1;
@@ -1303,17 +1303,17 @@ function HomeContent() {
13031303
if (connections.length === 0) {
13041304
// Create 2 new free-floating connections
13051305
connections = [
1306-
{ type: 'y' as const, targetX: n.position.x + 75, targetY: n.position.y - 50, label: 'Yes' },
1307-
{ type: 'n' as const, targetX: n.position.x + 75, targetY: n.position.y + 50, label: 'No' },
1306+
{ type: EdgeType.YES, targetX: n.position.x + 75, targetY: n.position.y - 50, label: 'Yes' },
1307+
{ type: EdgeType.NO, targetX: n.position.x + 75, targetY: n.position.y + 50, label: 'No' },
13081308
];
13091309
} else if (connections.length === 1) {
13101310
// Keep existing as YES, add NO
1311-
connections[0] = { ...connections[0], type: 'y' as const };
1312-
connections.push({ type: 'n' as const, targetX: n.position.x + 75, targetY: n.position.y + 50, label: 'No' });
1311+
connections[0] = { ...connections[0], type: EdgeType.YES };
1312+
connections.push({ type: EdgeType.NO, targetX: n.position.x + 75, targetY: n.position.y + 50, label: 'No' });
13131313
} else {
13141314
// Has 2+ connections: convert first to YES, second to NO, keep rest as-is
1315-
connections[0] = { ...connections[0], type: 'y' as const };
1316-
connections[1] = { ...connections[1], type: 'n' as const };
1315+
connections[0] = { ...connections[0], type: EdgeType.YES };
1316+
connections[1] = { ...connections[1], type: EdgeType.NO };
13171317
}
13181318

13191319
return {
@@ -1325,11 +1325,11 @@ function HomeContent() {
13251325
}
13261326

13271327
// Changing FROM question node to something else
1328-
if (oldType === 'n') {
1328+
if (oldType === NodeType.QUESTION) {
13291329
// Convert YES/NO connections to ALWAYS
13301330
const connections = n.connections.map(conn => ({
13311331
...conn,
1332-
type: '-' as const,
1332+
type: EdgeType.ALWAYS,
13331333
}));
13341334

13351335
return {
@@ -1348,12 +1348,12 @@ function HomeContent() {
13481348
});
13491349

13501350
// If changing FROM question, need to re-index remaining questions and update sliderValues
1351-
if (oldType === 'n') {
1351+
if (oldType === NodeType.QUESTION) {
13521352
const oldSliderIndex = node.sliderIndex;
13531353
if (oldSliderIndex !== null) {
13541354
// Re-index all questions that had higher indices
13551355
const finalNodes = updatedNodes.map(n => {
1356-
if (n.type === 'n' && n.sliderIndex !== null && n.sliderIndex > oldSliderIndex) {
1356+
if (n.type === NodeType.QUESTION && n.sliderIndex !== null && n.sliderIndex > oldSliderIndex) {
13571357
return { ...n, sliderIndex: n.sliderIndex - 1 };
13581358
}
13591359
return n;

components/AddArrowButtons.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ArrowUp, ArrowDown, ArrowLeft, ArrowRight } from 'lucide-react';
2+
import { NodeType as NT } from '@/lib/types';
23
import Tooltip from './Tooltip';
34

45
interface AddArrowButtonsProps {
@@ -19,7 +20,7 @@ export default function AddArrowButtons({
1920
const offset = 15; // Distance from node edge
2021

2122
// Determine if this is an outcome node (has colored bubbles on the left)
22-
const isOutcomeNode = nodeType === 'g' || nodeType === 'a' || nodeType === 'e';
23+
const isOutcomeNode = nodeType === NT.GOOD || nodeType === NT.AMBIVALENT || nodeType === NT.EXISTENTIAL;
2324

2425
const allButtons = [
2526
{

components/ConnectionEditor.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { useState } from 'react';
44
import type { GraphNode, NodeConnection, EdgeType } from '@/lib/types';
5+
import { EdgeType as ET } from '@/lib/types';
56

67
interface ConnectionEditorProps {
78
selectedNode: GraphNode | null;
@@ -46,11 +47,11 @@ export default function ConnectionEditor({
4647

4748
const getEdgeTypeLabel = (type: EdgeType): string => {
4849
switch (type) {
49-
case 'y':
50+
case ET.YES:
5051
return 'YES';
51-
case 'n':
52+
case ET.NO:
5253
return 'NO';
53-
case '-':
54+
case ET.ALWAYS:
5455
return 'ALWAYS';
5556
default:
5657
return type;
@@ -59,11 +60,11 @@ export default function ConnectionEditor({
5960

6061
const getEdgeTypeColor = (type: EdgeType): string => {
6162
switch (type) {
62-
case 'y':
63+
case ET.YES:
6364
return 'text-green-400';
64-
case 'n':
65+
case ET.NO:
6566
return 'text-red-400';
66-
case '-':
67+
case ET.ALWAYS:
6768
return 'text-blue-400';
6869
default:
6970
return 'text-gray-400';

components/Edge.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ export default function Edge({
277277
// Calculate label text
278278
let labelText = '';
279279
let labelDescription = '';
280-
if (yn !== ET.E100) {
280+
if (yn !== ET.ALWAYS) {
281281
const value = yn === ET.YES ? sliderValue : (sliderValue !== null ? 100 - sliderValue : null);
282282
if (value !== null) {
283283
// Get descriptive label from edge data

components/Flowchart.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import {
1010
ZOOM_SENSITIVITY,
1111
CANVAS_PADDING,
1212
NodeType as NT,
13+
EdgeType as ET,
1314
type GraphData,
15+
type NodeType as NodeTypeValue,
1416
} from "@/lib/types";
1517
import NodeComponent from "./Node";
1618
import EdgeComponent from "./Edge";
@@ -62,10 +64,7 @@ interface FlowchartProps {
6264
onAddNode?: (x: number, y: number) => void;
6365
onNodeSelect?: (nodeId: string | null) => void;
6466
onDeleteNode?: (nodeId: string) => void;
65-
onChangeNodeType?: (
66-
nodeId: string,
67-
newType: "n" | "i" | "g" | "a" | "e"
68-
) => void;
67+
onChangeNodeType?: (nodeId: string, newType: NodeTypeValue) => void;
6968
onEdgeClick?: (edgeIndex: number) => void;
7069
onEdgeReconnect?: (
7170
edgeIndex: number,
@@ -475,7 +474,6 @@ export default function Flowchart({
475474
onBackgroundClick,
476475
selectedEdgeIndex,
477476
selectedNodeId,
478-
editorCloseTimestampRef,
479477
]
480478
);
481479

@@ -683,7 +681,7 @@ export default function Flowchart({
683681
const noEdgesFromNode = edges.filter(
684682
(e) =>
685683
nodes[e.source].id === sourceNode.id &&
686-
e.yn === "n" &&
684+
e.yn === ET.NO &&
687685
e.label === "No"
688686
);
689687
const newestNoEdge =
@@ -777,7 +775,9 @@ export default function Flowchart({
777775
const outgoingEdges = edges.filter((e) => e.source === node.index);
778776
const isIntermediate = outgoingEdges.length === 1;
779777
const isOutcomeWithNoArrows =
780-
(node.type === "g" || node.type === "a" || node.type === "e") &&
778+
(node.type === NT.GOOD ||
779+
node.type === NT.AMBIVALENT ||
780+
node.type === NT.EXISTENTIAL) &&
781781
outgoingEdges.length === 0;
782782
const shouldShowAddArrows =
783783
node.type !== NT.START &&

0 commit comments

Comments
 (0)