Canvas: Prettier

Change-Id: I620dde109df0f29f0c85c6fe150e347d2c32a03e
diff --git a/apps/canvas/front/src/lib/state.ts b/apps/canvas/front/src/lib/state.ts
index 2492642..0749fbb 100644
--- a/apps/canvas/front/src/lib/state.ts
+++ b/apps/canvas/front/src/lib/state.ts
@@ -1,275 +1,302 @@
-import { v4 as uuidv4 } from "uuid";
-import { create } from 'zustand';
-import { addEdge, applyNodeChanges, applyEdgeChanges, Connection, EdgeChange, useNodes } from '@xyflow/react';
-import type {
-  Edge,
-  Node,
-  OnNodesChange,
-  OnEdgesChange,
-  OnConnect,
-} from '@xyflow/react';
-import type { DeepPartial } from "react-hook-form";
 import { Category, defaultCategories } from "./categories";
 import { CreateValidators, Validator } from "./config";
+import { GitHubService, GitHubServiceImpl } from "./github";
+import type { Edge, Node, OnConnect, OnEdgesChange, OnNodesChange } from "@xyflow/react";
+import { addEdge, applyEdgeChanges, applyNodeChanges, Connection, EdgeChange, useNodes } from "@xyflow/react";
+import type { DeepPartial } from "react-hook-form";
+import { v4 as uuidv4 } from "uuid";
 import { z } from "zod";
-import { GitHubService, GitHubServiceImpl } from './github';
+import { create } from "zustand";
 
 export type InitData = {
-  label: string;
-  envVars: BoundEnvVar[];
-  ports: Port[];
+	label: string;
+	envVars: BoundEnvVar[];
+	ports: Port[];
 };
 
 export type NodeData = InitData & {
-  activeField?: string | undefined;
-  state: string | null;
+	activeField?: string | undefined;
+	state: string | null;
 };
 
 export type PortConnectedTo = {
-  serviceId: string;
-  portId: string;
-}
+	serviceId: string;
+	portId: string;
+};
 
 export type NetworkData = NodeData & {
-  domain: string;
+	domain: string;
 };
 
 export type NetworkNode = Node<NetworkData> & {
-  type: "network";
+	type: "network";
 };
 
 export type GatewayHttpsData = NodeData & {
-  network?: string;
-  subdomain?: string;
-  https?: PortConnectedTo;
-  auth?: {
-    enabled: boolean;
-    groups: string[];
-    noAuthPathPatterns: string[];
-  }
+	network?: string;
+	subdomain?: string;
+	https?: PortConnectedTo;
+	auth?: {
+		enabled: boolean;
+		groups: string[];
+		noAuthPathPatterns: string[];
+	};
 };
 
 export type GatewayHttpsNode = Node<GatewayHttpsData> & {
-  type: "gateway-https";
+	type: "gateway-https";
 };
 
 export type GatewayTCPData = NodeData & {
-  network?: string;
-  subdomain?: string;
-  exposed: PortConnectedTo[];
-  selected?: {
-    serviceId?: string;
-    portId?: string;
-  };
+	network?: string;
+	subdomain?: string;
+	exposed: PortConnectedTo[];
+	selected?: {
+		serviceId?: string;
+		portId?: string;
+	};
 };
 
 export type GatewayTCPNode = Node<GatewayTCPData> & {
-  type: "gateway-tcp";
+	type: "gateway-tcp";
 };
 
 export type Port = {
-  id: string;
-  name: string;
-  value: number;
+	id: string;
+	name: string;
+	value: number;
 };
 
 export const ServiceTypes = [
-  "deno:2.2.0",
-  "golang:1.20.0",
-  "golang:1.22.0",
-  "golang:1.24.0",
-  "hugo:latest",
-  "php:8.2-apache",
-  "nextjs:deno-2.0.0",
-  "node-23.1.0"
+	"deno:2.2.0",
+	"golang:1.20.0",
+	"golang:1.22.0",
+	"golang:1.24.0",
+	"hugo:latest",
+	"php:8.2-apache",
+	"nextjs:deno-2.0.0",
+	"node-23.1.0",
 ] as const;
-export type ServiceType = typeof ServiceTypes[number];
+export type ServiceType = (typeof ServiceTypes)[number];
 
 export type ServiceData = NodeData & {
-  type: ServiceType;
-  repository: {
-    id: string;
-  } | {
-    id: string;
-    branch: string;
-  } | {
-    id: string;
-    branch: string;
-    rootDir: string;
-  };
-  env: string[];
-  volume: string[];
-  preBuildCommands: string;
-  isChoosingPortToConnect: boolean;
+	type: ServiceType;
+	repository:
+		| {
+				id: string;
+		  }
+		| {
+				id: string;
+				branch: string;
+		  }
+		| {
+				id: string;
+				branch: string;
+				rootDir: string;
+		  };
+	env: string[];
+	volume: string[];
+	preBuildCommands: string;
+	isChoosingPortToConnect: boolean;
 };
 
 export type ServiceNode = Node<ServiceData> & {
-  type: "app";
+	type: "app";
 };
 
 export type VolumeType = "ReadWriteOnce" | "ReadOnlyMany" | "ReadWriteMany" | "ReadWriteOncePod";
 
 export type VolumeData = NodeData & {
-  type: VolumeType;
-  size: string;
-  attachedTo: string[];
+	type: VolumeType;
+	size: string;
+	attachedTo: string[];
 };
 
 export type VolumeNode = Node<VolumeData> & {
-  type: "volume";
+	type: "volume";
 };
 
 export type PostgreSQLData = NodeData & {
-  volumeId: string;
+	volumeId: string;
 };
 
 export type PostgreSQLNode = Node<PostgreSQLData> & {
-  type: "postgresql";
+	type: "postgresql";
 };
 
 export type MongoDBData = NodeData & {
-  volumeId: string;
+	volumeId: string;
 };
 
 export type MongoDBNode = Node<MongoDBData> & {
-  type: "mongodb";
+	type: "mongodb";
 };
 
 export type GithubData = NodeData & {
-  repository?: {
-    id: number;
-    sshURL: string;
-  };
+	repository?: {
+		id: number;
+		sshURL: string;
+	};
 };
 
 export type GithubNode = Node<GithubData> & {
-  type: "github";
+	type: "github";
 };
 
 export type NANode = Node<NodeData> & {
-  type: undefined;
+	type: undefined;
 };
 
-export type AppNode = NetworkNode | GatewayHttpsNode | GatewayTCPNode | ServiceNode | VolumeNode | PostgreSQLNode | MongoDBNode | GithubNode | NANode;
+export type AppNode =
+	| NetworkNode
+	| GatewayHttpsNode
+	| GatewayTCPNode
+	| ServiceNode
+	| VolumeNode
+	| PostgreSQLNode
+	| MongoDBNode
+	| GithubNode
+	| NANode;
 
 export function nodeLabel(n: AppNode): string {
-  switch (n.type) {
-    case "network": return n.data.domain;
-    case "app": return n.data.label || "Service";
-    case "github": return n.data.repository?.sshURL || "Github";
-    case "gateway-https": {
-      if (n.data && n.data.network && n.data.subdomain) {
-        return `https://${n.data.subdomain}.${n.data.network}`;
-      } else {
-        return "HTTPS Gateway";
-      }
-    }
-    case "gateway-tcp": {
-      if (n.data && n.data.network && n.data.subdomain) {
-        return `${n.data.subdomain}.${n.data.network}`;
-      } else {
-        return "TCP Gateway";
-      }
-    }
-    case "mongodb": return n.data.label || "MongoDB";
-    case "postgresql": return n.data.label || "PostgreSQL";
-    case "volume": return n.data.label || "Volume";
-    case undefined: throw new Error("MUST NOT REACH!");
-  }
+	switch (n.type) {
+		case "network":
+			return n.data.domain;
+		case "app":
+			return n.data.label || "Service";
+		case "github":
+			return n.data.repository?.sshURL || "Github";
+		case "gateway-https": {
+			if (n.data && n.data.network && n.data.subdomain) {
+				return `https://${n.data.subdomain}.${n.data.network}`;
+			} else {
+				return "HTTPS Gateway";
+			}
+		}
+		case "gateway-tcp": {
+			if (n.data && n.data.network && n.data.subdomain) {
+				return `${n.data.subdomain}.${n.data.network}`;
+			} else {
+				return "TCP Gateway";
+			}
+		}
+		case "mongodb":
+			return n.data.label || "MongoDB";
+		case "postgresql":
+			return n.data.label || "PostgreSQL";
+		case "volume":
+			return n.data.label || "Volume";
+		case undefined:
+			throw new Error("MUST NOT REACH!");
+	}
 }
 
 export function nodeIsConnectable(n: AppNode, handle: string): boolean {
-  switch (n.type) {
-    case "network":
-      return true;
-    case "app":
-      if (handle === "ports") {
-        return n.data !== undefined && n.data.ports !== undefined && n.data.ports.length > 0;
-      } else if (handle === "repository") {
-        if (!n.data || !n.data.repository || !n.data.repository.id) {
-          return true;
-        }
-        return false;
-      }
-      return false;
-    case "github":
-      if (n.data.repository?.id !== undefined) {
-        return true;
-      }
-      return false;
-    case "gateway-https":
-      if (handle === "subdomain") {
-        return n.data.network === undefined;
-      }
-      return n.data === undefined || n.data.https === undefined;
-    case "gateway-tcp":
-      if (handle === "subdomain") {
-        return n.data.network === undefined;
-      }
-      return true;
-    case "mongodb":
-      return true;
-    case "postgresql":
-      return true;
-    case "volume":
-      if (n.data === undefined || n.data.type === undefined) {
-        return false;
-      }
-      if (n.data.type === "ReadWriteOnce" || n.data.type === "ReadWriteOncePod") {
-        return n.data.attachedTo === undefined || n.data.attachedTo.length === 0;
-      }
-      return true;
-    case undefined: throw new Error("MUST NOT REACH!");
-  }
+	switch (n.type) {
+		case "network":
+			return true;
+		case "app":
+			if (handle === "ports") {
+				return n.data !== undefined && n.data.ports !== undefined && n.data.ports.length > 0;
+			} else if (handle === "repository") {
+				if (!n.data || !n.data.repository || !n.data.repository.id) {
+					return true;
+				}
+				return false;
+			}
+			return false;
+		case "github":
+			if (n.data.repository?.id !== undefined) {
+				return true;
+			}
+			return false;
+		case "gateway-https":
+			if (handle === "subdomain") {
+				return n.data.network === undefined;
+			}
+			return n.data === undefined || n.data.https === undefined;
+		case "gateway-tcp":
+			if (handle === "subdomain") {
+				return n.data.network === undefined;
+			}
+			return true;
+		case "mongodb":
+			return true;
+		case "postgresql":
+			return true;
+		case "volume":
+			if (n.data === undefined || n.data.type === undefined) {
+				return false;
+			}
+			if (n.data.type === "ReadWriteOnce" || n.data.type === "ReadWriteOncePod") {
+				return n.data.attachedTo === undefined || n.data.attachedTo.length === 0;
+			}
+			return true;
+		case undefined:
+			throw new Error("MUST NOT REACH!");
+	}
 }
 
-export type BoundEnvVar = {
-  id: string;
-  source: string | null;
-} | {
-  id: string;
-  source: string | null;
-  name: string;
-  isEditting: boolean;
-} | {
-  id: string;
-  source: string | null;
-  name: string;
-  alias: string;
-  isEditting: boolean;
-} | {
-  id: string;
-  source: string | null;
-  portId: string;
-  name: string;
-  alias: string;
-  isEditting: boolean;
-};
+export type BoundEnvVar =
+	| {
+			id: string;
+			source: string | null;
+	  }
+	| {
+			id: string;
+			source: string | null;
+			name: string;
+			isEditting: boolean;
+	  }
+	| {
+			id: string;
+			source: string | null;
+			name: string;
+			alias: string;
+			isEditting: boolean;
+	  }
+	| {
+			id: string;
+			source: string | null;
+			portId: string;
+			name: string;
+			alias: string;
+			isEditting: boolean;
+	  };
 
 export type EnvVar = {
-  name: string;
-  value: string;
+	name: string;
+	value: string;
 };
 
 export function nodeEnvVarNamePort(n: AppNode, portName: string): string {
-  return `DODO_SERVICE_${n.data.label.toUpperCase()}_ADDRESS_${portName.toUpperCase()}`;
+	return `DODO_SERVICE_${n.data.label.toUpperCase()}_ADDRESS_${portName.toUpperCase()}`;
 }
 
 export function nodeEnvVarNames(n: AppNode): string[] {
-  switch (n.type) {
-    case "app": return [
-      `DODO_SERVICE_${n.data.label.toUpperCase()}_ADDRESS`,
-      ...(n.data.ports || []).map((p) => nodeEnvVarNamePort(n, p.name)),
-    ];
-    case "github": return [];
-    case "gateway-https": return [];
-    case "gateway-tcp": return [];
-    case "mongodb": return [`DODO_MONGODB_${n.data.label.toUpperCase()}_URL`];
-    case "postgresql": return [`DODO_POSTGRESQL_${n.data.label.toUpperCase()}_URL`];
-    case "volume": return [`DODO_VOLUME_${n.data.label.toUpperCase()}_PATH`];
-    case undefined: throw new Error("MUST NOT REACH");
-    default: throw new Error("MUST NOT REACH");
-  }
+	switch (n.type) {
+		case "app":
+			return [
+				`DODO_SERVICE_${n.data.label.toUpperCase()}_ADDRESS`,
+				...(n.data.ports || []).map((p) => nodeEnvVarNamePort(n, p.name)),
+			];
+		case "github":
+			return [];
+		case "gateway-https":
+			return [];
+		case "gateway-tcp":
+			return [];
+		case "mongodb":
+			return [`DODO_MONGODB_${n.data.label.toUpperCase()}_URL`];
+		case "postgresql":
+			return [`DODO_POSTGRESQL_${n.data.label.toUpperCase()}_URL`];
+		case "volume":
+			return [`DODO_VOLUME_${n.data.label.toUpperCase()}_PATH`];
+		case undefined:
+			throw new Error("MUST NOT REACH");
+		default:
+			throw new Error("MUST NOT REACH");
+	}
 }
 
 export type NodeType = Exclude<Pick<AppNode, "type">["type"], undefined>;
@@ -277,68 +304,72 @@
 export type MessageType = "INFO" | "WARNING" | "FATAL";
 
 export type Message = {
-  id: string;
-  type: MessageType;
-  nodeId?: string;
-  message: string;
-  onHighlight?: (state: AppState) => void;
-  onLooseHighlight?: (state: AppState) => void;
-  onClick?: (state: AppState) => void;
+	id: string;
+	type: MessageType;
+	nodeId?: string;
+	message: string;
+	onHighlight?: (state: AppState) => void;
+	onLooseHighlight?: (state: AppState) => void;
+	onClick?: (state: AppState) => void;
 };
 
 export const envSchema = z.object({
-  deployKey: z.optional(z.string().min(1)),
-  networks: z.array(z.object({
-    name: z.string().min(1),
-    domain: z.string().min(1),
-  })).default([]),
-  integrations: z.object({
-    github: z.boolean(),
-  }),
+	deployKey: z.optional(z.string().min(1)),
+	networks: z
+		.array(
+			z.object({
+				name: z.string().min(1),
+				domain: z.string().min(1),
+			}),
+		)
+		.default([]),
+	integrations: z.object({
+		github: z.boolean(),
+	}),
 });
 
 export type Env = z.infer<typeof envSchema>;
 
 const defaultEnv: Env = {
-  deployKey: undefined,
-  networks: [],
-  integrations: {
-    github: false,
-  }
+	deployKey: undefined,
+	networks: [],
+	integrations: {
+		github: false,
+	},
 };
 
 export type Project = {
-  id: string;
-  name: string;
-}
+	id: string;
+	name: string;
+};
 
 export type IntegrationsConfig = {
-  github: boolean;
+	github: boolean;
 };
 
 type NodeUpdate<T extends NodeType> = DeepPartial<Extract<AppNode, { type: T }>>;
 type NodeDataUpdate<T extends NodeType> = DeepPartial<Extract<AppNode, { type: T }>["data"]>;
 
 export type AppState = {
-  projectId: string | undefined;
-  projects: Project[];
-  nodes: AppNode[];
-  edges: Edge[];
-  categories: Category[];
-  messages: Message[];
-  env: Env;
-  githubService: GitHubService | null;
-  setHighlightCategory: (name: string, active: boolean) => void;
-  onNodesChange: OnNodesChange<AppNode>;
-  onEdgesChange: OnEdgesChange;
-  onConnect: OnConnect;
-  setNodes: (nodes: AppNode[]) => void;
-  setEdges: (edges: Edge[]) => void;
-  setProject: (projectId: string | undefined) => void;
-  updateNode: <T extends NodeType>(id: string, node: NodeUpdate<T>) => void;
-  updateNodeData: <T extends NodeType>(id: string, data: NodeDataUpdate<T>) => void;
-  replaceEdge: (c: Connection, id?: string) => void;
-  refreshEnv: () => Promise<void>;
+	projectId: string | undefined;
+	projects: Project[];
+	nodes: AppNode[];
+	edges: Edge[];
+	categories: Category[];
+	messages: Message[];
+	env: Env;
+	githubService: GitHubService | null;
+	setHighlightCategory: (name: string, active: boolean) => void;
+	onNodesChange: OnNodesChange<AppNode>;
+	onEdgesChange: OnEdgesChange;
+	onConnect: OnConnect;
+	setNodes: (nodes: AppNode[]) => void;
+	setEdges: (edges: Edge[]) => void;
+	setProject: (projectId: string | undefined) => void;
+	updateNode: <T extends NodeType>(id: string, node: NodeUpdate<T>) => void;
+	updateNodeData: <T extends NodeType>(id: string, data: NodeDataUpdate<T>) => void;
+	replaceEdge: (c: Connection, id?: string) => void;
+	refreshEnv: () => Promise<void>;
 };
 
 const projectIdSelector = (state: AppState) => state.projectId;
@@ -348,326 +379,325 @@
 const envSelector = (state: AppState) => state.env;
 
 export function useProjectId(): string | undefined {
-  return useStateStore(projectIdSelector);
+	return useStateStore(projectIdSelector);
 }
 
 export function useCategories(): Category[] {
-  return useStateStore(categoriesSelector);
+	return useStateStore(categoriesSelector);
 }
 
 export function useMessages(): Message[] {
-  return useStateStore(messagesSelector);
+	return useStateStore(messagesSelector);
 }
 
 export function useNodeMessages(id: string): Message[] {
-  return useMessages().filter((m) => m.nodeId === id);
+	return useMessages().filter((m) => m.nodeId === id);
 }
 
 export function useNodeLabel(id: string): string {
-  return nodeLabel(useNodes<AppNode>().find((n) => n.id === id)!);
+	return nodeLabel(useNodes<AppNode>().find((n) => n.id === id)!);
 }
 
 export function useNodePortName(id: string, portId: string): string {
-  return (useNodes<AppNode>().find((n) => n.id === id)!.data.ports || []).find((p) => p.id === portId)!.name;
+	return (useNodes<AppNode>().find((n) => n.id === id)!.data.ports || []).find((p) => p.id === portId)!.name;
 }
 
 export function useEnv(): Env {
-  return useStateStore(envSelector);
+	return useStateStore(envSelector);
 }
 
 export function useGithubService(): GitHubService | null {
-  return useStateStore(githubServiceSelector);
+	return useStateStore(githubServiceSelector);
 }
 
 const v: Validator = CreateValidators();
 
 export const useStateStore = create<AppState>((set, get): AppState => {
-  const setN = (nodes: AppNode[]) => {
-    set((state) => ({
-      ...state,
-      nodes,
-    }));
-  };
+	const setN = (nodes: AppNode[]) => {
+		set((state) => ({
+			...state,
+			nodes,
+		}));
+	};
 
-  function updateNodeData<T extends NodeType>(id: string, data: NodeDataUpdate<T>): void {
-    setN(
-      get().nodes.map((n) => {
-        if (n.id === id) {
-          return {
-            ...n,
-            data: {
-              ...n.data,
-              ...data,
-            },
-          } as Extract<AppNode, { type: T }>;
-        }
-        return n;
-      })
-    );
-  }
+	function updateNodeData<T extends NodeType>(id: string, data: NodeDataUpdate<T>): void {
+		setN(
+			get().nodes.map((n) => {
+				if (n.id === id) {
+					return {
+						...n,
+						data: {
+							...n.data,
+							...data,
+						},
+					} as Extract<AppNode, { type: T }>;
+				}
+				return n;
+			}),
+		);
+	}
 
-  function updateNode<T extends NodeType>(id: string, node: NodeUpdate<T>): void {
-    setN(
-      get().nodes.map((n) => {
-        if (n.id === id) {
-          return {
-            ...n,
-            ...node,
-          } as Extract<AppNode, { type: T }>;
-        }
-        return n;
-      })
-    );
-  }
+	function updateNode<T extends NodeType>(id: string, node: NodeUpdate<T>): void {
+		setN(
+			get().nodes.map((n) => {
+				if (n.id === id) {
+					return {
+						...n,
+						...node,
+					} as Extract<AppNode, { type: T }>;
+				}
+				return n;
+			}),
+		);
+	}
 
-  function onConnect(c: Connection) {
-    const { nodes, edges } = get();
-    set({
-      edges: addEdge(c, edges),
-    });
-    const sn = nodes.filter((n) => n.id === c.source)[0]!;
-    const tn = nodes.filter((n) => n.id === c.target)[0]!;
-    if (tn.type === "network") {
-      if (sn.type === "gateway-https") {
-        updateNodeData<"gateway-https">(sn.id, {
-          network: tn.data.domain,
-        });
-      } else if (sn.type === "gateway-tcp") {
-        updateNodeData<"gateway-tcp">(sn.id, {
-          network: tn.data.domain,
-        });
-      }
-    }
-    if (tn.type === "app") {
-      if (c.sourceHandle === "env_var" && c.targetHandle === "env_var") {
-        const sourceEnvVars = nodeEnvVarNames(sn);
-        if (sourceEnvVars.length === 0) {
-          throw new Error("MUST NOT REACH!");
-        }
-        const id = uuidv4();
-        if (sourceEnvVars.length === 1) {
-          updateNode<"app">(c.target, {
-            ...tn,
-            data: {
-              ...tn.data,
-              envVars: [
-                ...(tn.data.envVars || []),
-                {
-                  id: id,
-                  source: c.source,
-                  name: sourceEnvVars[0],
-                  isEditting: false,
-                },
-              ],
-            },
-          });
-        } else {
-          updateNode<"app">(c.target, {
-            ...tn,
-            data: {
-              ...tn.data,
-              envVars: [
-                ...(tn.data.envVars || []),
-                {
-                  id: id,
-                  source: c.source,
-                },
-              ],
-            },
-          });
-        }
-      }
-      if (c.sourceHandle === "ports" && c.targetHandle === "env_var") {
-        const sourcePorts = sn.data.ports || [];
-        const id = uuidv4();
-        if (sourcePorts.length === 1) {
-          updateNode<"app">(c.target, {
-            ...tn,
-            data: {
-              ...tn.data,
-              envVars: [
-                ...(tn.data.envVars || []),
-                {
-                  id: id,
-                  source: c.source,
-                  name: nodeEnvVarNamePort(sn, sourcePorts[0].name),
-                  portId: sourcePorts[0].id,
-                  isEditting: false,
-                },
-              ],
-            },
-          });
-        }
-      }
-    }
-    if (c.sourceHandle === "volume") {
-      updateNodeData<"volume">(c.source, {
-        attachedTo: ((sn as VolumeNode).data.attachedTo || []).concat(c.source),
-      });
-    }
-    if (c.targetHandle === "volume") {
-      if (tn.type === "postgresql" || tn.type === "mongodb") {
-        updateNodeData(c.target, {
-          volumeId: c.source,
-        });
-      }
-    }
-    if (c.targetHandle === "https") {
-      if ((sn.data.ports || []).length === 1) {
-        updateNodeData<"gateway-https">(c.target, {
-          https: {
-            serviceId: c.source,
-            portId: sn.data.ports![0].id,
-          }
-        });
-      } else {
-        updateNodeData<"gateway-https">(c.target, {
-          https: {
-            serviceId: c.source,
-            portId: "", // TODO(gio)
-          }
-        });
-      }
-    }
-    if (c.targetHandle === "tcp") {
-      const td = tn.data as GatewayTCPData;
-      if ((sn.data.ports || []).length === 1) {
-        updateNodeData<"gateway-tcp">(c.target, {
-          exposed: (td.exposed || []).concat({
-            serviceId: c.source,
-            portId: sn.data.ports![0].id,
-          }),
-        });
-      } else {
-        updateNodeData<"gateway-tcp">(c.target, {
-          selected: {
-            serviceId: c.source,
-            portId: undefined,
-          },
-        });
-      }
-    }
-    if (sn.type === "app") {
-      if (c.sourceHandle === "ports") {
-        updateNodeData<"app">(sn.id, {
-          isChoosingPortToConnect: true,
-        });
-      }
-    }
-    if (tn.type === "app") {
-      if (c.targetHandle === "repository") {
-        updateNodeData<"app">(tn.id, {
-          repository: {
-            id: c.source,
-            branch: "master",
-            rootDir: "/",
-          }
-        });
-      }
-    }
-  }
-  return {
-    projectId: undefined,
-    projects: [],
-    nodes: [],
-    edges: [],
-    categories: defaultCategories,
-    messages: v([]),
-    env: defaultEnv,
-    githubService: null,
-    setHighlightCategory: (name, active) => {
-      set({
-        categories: get().categories.map(
-          (c) => {
-            if (c.title.toLowerCase() !== name.toLowerCase()) {
-              return c;
-            } else {
-              return {
-                ...c,
-                active,
-              }
-            }
-          })
-      });
-    },
-    onNodesChange: (changes) => {
-      const nodes = applyNodeChanges(changes, get().nodes);
-      setN(nodes);
-    },
-    onEdgesChange: (changes) => {
-      set({
-        edges: applyEdgeChanges(changes, get().edges),
-      });
-    },
-    setNodes: (nodes) => {
-      setN(nodes);
-    },
-    setEdges: (edges) => {
-      set({ edges });
-    },
-    replaceEdge: (c, id) => {
-      let change: EdgeChange;
-      if (id === undefined) {
-        change = {
-          type: "add",
-          item: {
-            id: uuidv4(),
-            ...c,
-          }
-        };
-        onConnect(c);
-      } else {
-        change = {
-          type: "replace",
-          id,
-          item: {
-            id,
-            ...c,
-          }
-        };
-      }
-      set({
-        edges: applyEdgeChanges([change], get().edges),
-      })
-    },
-    updateNode,
-    updateNodeData,
-    onConnect,
-    refreshEnv: async () => {
-      const projectId = get().projectId;
-      let env: Env = defaultEnv;
+	function onConnect(c: Connection) {
+		const { nodes, edges } = get();
+		set({
+			edges: addEdge(c, edges),
+		});
+		const sn = nodes.filter((n) => n.id === c.source)[0]!;
+		const tn = nodes.filter((n) => n.id === c.target)[0]!;
+		if (tn.type === "network") {
+			if (sn.type === "gateway-https") {
+				updateNodeData<"gateway-https">(sn.id, {
+					network: tn.data.domain,
+				});
+			} else if (sn.type === "gateway-tcp") {
+				updateNodeData<"gateway-tcp">(sn.id, {
+					network: tn.data.domain,
+				});
+			}
+		}
+		if (tn.type === "app") {
+			if (c.sourceHandle === "env_var" && c.targetHandle === "env_var") {
+				const sourceEnvVars = nodeEnvVarNames(sn);
+				if (sourceEnvVars.length === 0) {
+					throw new Error("MUST NOT REACH!");
+				}
+				const id = uuidv4();
+				if (sourceEnvVars.length === 1) {
+					updateNode<"app">(c.target, {
+						...tn,
+						data: {
+							...tn.data,
+							envVars: [
+								...(tn.data.envVars || []),
+								{
+									id: id,
+									source: c.source,
+									name: sourceEnvVars[0],
+									isEditting: false,
+								},
+							],
+						},
+					});
+				} else {
+					updateNode<"app">(c.target, {
+						...tn,
+						data: {
+							...tn.data,
+							envVars: [
+								...(tn.data.envVars || []),
+								{
+									id: id,
+									source: c.source,
+								},
+							],
+						},
+					});
+				}
+			}
+			if (c.sourceHandle === "ports" && c.targetHandle === "env_var") {
+				const sourcePorts = sn.data.ports || [];
+				const id = uuidv4();
+				if (sourcePorts.length === 1) {
+					updateNode<"app">(c.target, {
+						...tn,
+						data: {
+							...tn.data,
+							envVars: [
+								...(tn.data.envVars || []),
+								{
+									id: id,
+									source: c.source,
+									name: nodeEnvVarNamePort(sn, sourcePorts[0].name),
+									portId: sourcePorts[0].id,
+									isEditting: false,
+								},
+							],
+						},
+					});
+				}
+			}
+		}
+		if (c.sourceHandle === "volume") {
+			updateNodeData<"volume">(c.source, {
+				attachedTo: ((sn as VolumeNode).data.attachedTo || []).concat(c.source),
+			});
+		}
+		if (c.targetHandle === "volume") {
+			if (tn.type === "postgresql" || tn.type === "mongodb") {
+				updateNodeData(c.target, {
+					volumeId: c.source,
+				});
+			}
+		}
+		if (c.targetHandle === "https") {
+			if ((sn.data.ports || []).length === 1) {
+				updateNodeData<"gateway-https">(c.target, {
+					https: {
+						serviceId: c.source,
+						portId: sn.data.ports![0].id,
+					},
+				});
+			} else {
+				updateNodeData<"gateway-https">(c.target, {
+					https: {
+						serviceId: c.source,
+						portId: "", // TODO(gio)
+					},
+				});
+			}
+		}
+		if (c.targetHandle === "tcp") {
+			const td = tn.data as GatewayTCPData;
+			if ((sn.data.ports || []).length === 1) {
+				updateNodeData<"gateway-tcp">(c.target, {
+					exposed: (td.exposed || []).concat({
+						serviceId: c.source,
+						portId: sn.data.ports![0].id,
+					}),
+				});
+			} else {
+				updateNodeData<"gateway-tcp">(c.target, {
+					selected: {
+						serviceId: c.source,
+						portId: undefined,
+					},
+				});
+			}
+		}
+		if (sn.type === "app") {
+			if (c.sourceHandle === "ports") {
+				updateNodeData<"app">(sn.id, {
+					isChoosingPortToConnect: true,
+				});
+			}
+		}
+		if (tn.type === "app") {
+			if (c.targetHandle === "repository") {
+				updateNodeData<"app">(tn.id, {
+					repository: {
+						id: c.source,
+						branch: "master",
+						rootDir: "/",
+					},
+				});
+			}
+		}
+	}
+	return {
+		projectId: undefined,
+		projects: [],
+		nodes: [],
+		edges: [],
+		categories: defaultCategories,
+		messages: v([]),
+		env: defaultEnv,
+		githubService: null,
+		setHighlightCategory: (name, active) => {
+			set({
+				categories: get().categories.map((c) => {
+					if (c.title.toLowerCase() !== name.toLowerCase()) {
+						return c;
+					} else {
+						return {
+							...c,
+							active,
+						};
+					}
+				}),
+			});
+		},
+		onNodesChange: (changes) => {
+			const nodes = applyNodeChanges(changes, get().nodes);
+			setN(nodes);
+		},
+		onEdgesChange: (changes) => {
+			set({
+				edges: applyEdgeChanges(changes, get().edges),
+			});
+		},
+		setNodes: (nodes) => {
+			setN(nodes);
+		},
+		setEdges: (edges) => {
+			set({ edges });
+		},
+		replaceEdge: (c, id) => {
+			let change: EdgeChange;
+			if (id === undefined) {
+				change = {
+					type: "add",
+					item: {
+						id: uuidv4(),
+						...c,
+					},
+				};
+				onConnect(c);
+			} else {
+				change = {
+					type: "replace",
+					id,
+					item: {
+						id,
+						...c,
+					},
+				};
+			}
+			set({
+				edges: applyEdgeChanges([change], get().edges),
+			});
+		},
+		updateNode,
+		updateNodeData,
+		onConnect,
+		refreshEnv: async () => {
+			const projectId = get().projectId;
+			let env: Env = defaultEnv;
 
-      try {
-        if (projectId) {
-          const response = await fetch(`/api/project/${projectId}/env`);
-          if (response.ok) {
-            const data = await response.json();
-            const result = envSchema.safeParse(data);
-            if (result.success) {
-              env = result.data;
-            } else {
-              console.error("Invalid env data:", result.error);
-            }
-          }
-        }
-      } catch (error) {
-        console.error("Failed to fetch integrations:", error);
-      } finally {
-        set({ env: env });
-        if (env.integrations.github) {
-          set({ githubService: new GitHubServiceImpl(projectId!) });
-        } else {
-          set({ githubService: null });
-        }
-      }
-    },
-    setProject: (projectId) => {
-      set({
-        projectId,
-      });
-      if (projectId) {
-        get().refreshEnv();
-      }
-    },
-  };
+			try {
+				if (projectId) {
+					const response = await fetch(`/api/project/${projectId}/env`);
+					if (response.ok) {
+						const data = await response.json();
+						const result = envSchema.safeParse(data);
+						if (result.success) {
+							env = result.data;
+						} else {
+							console.error("Invalid env data:", result.error);
+						}
+					}
+				}
+			} catch (error) {
+				console.error("Failed to fetch integrations:", error);
+			} finally {
+				set({ env: env });
+				if (env.integrations.github) {
+					set({ githubService: new GitHubServiceImpl(projectId!) });
+				} else {
+					set({ githubService: null });
+				}
+			}
+		},
+		setProject: (projectId) => {
+			set({
+				projectId,
+			});
+			if (projectId) {
+				get().refreshEnv();
+			}
+		},
+	};
 });