Canvas: Prettier

Change-Id: I620dde109df0f29f0c85c6fe150e347d2c32a03e
diff --git a/apps/canvas/front/src/components/node-app.tsx b/apps/canvas/front/src/components/node-app.tsx
index 3027f27..cfbbbde 100644
--- a/apps/canvas/front/src/components/node-app.tsx
+++ b/apps/canvas/front/src/components/node-app.tsx
@@ -1,13 +1,24 @@
 import { v4 as uuidv4 } from "uuid";
-import { NodeRect } from './node-rect';
-import { useStateStore, ServiceNode, ServiceTypes, nodeLabel, BoundEnvVar, AppState, nodeIsConnectable, GatewayTCPNode, GatewayHttpsNode, AppNode } from '@/lib/state';
-import { KeyboardEvent, FocusEvent, useCallback, useEffect, useMemo, useState } from 'react';
+import { NodeRect } from "./node-rect";
+import {
+	useStateStore,
+	ServiceNode,
+	ServiceTypes,
+	nodeLabel,
+	BoundEnvVar,
+	AppState,
+	nodeIsConnectable,
+	GatewayTCPNode,
+	GatewayHttpsNode,
+	AppNode,
+} from "@/lib/state";
+import { KeyboardEvent, FocusEvent, useCallback, useEffect, useMemo, useState } from "react";
 import { z } from "zod";
-import { DeepPartial, EventType, useForm, ControllerRenderProps, FieldPath } from 'react-hook-form';
-import { zodResolver } from '@hookform/resolvers/zod';
-import { Form, FormControl, FormField, FormItem, FormMessage } from './ui/form';
-import { Input } from './ui/input';
-import { Button } from './ui/button';
+import { DeepPartial, EventType, useForm, ControllerRenderProps, FieldPath } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { Form, FormControl, FormField, FormItem, FormMessage } from "./ui/form";
+import { Input } from "./ui/input";
+import { Button } from "./ui/button";
 import { Handle, Position, useNodes } from "@xyflow/react";
 import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
 import { PencilIcon, XIcon } from "lucide-react";
@@ -15,480 +26,577 @@
 import { Textarea } from "./ui/textarea";
 
 export function NodeApp(node: ServiceNode) {
-  const { id, selected } = node;
-  const isConnectablePorts = useMemo(() => nodeIsConnectable(node, "ports"), [node]);
-  const isConnectableRepository = useMemo(() => nodeIsConnectable(node, "repository"), [node]);
-  return (
-    <NodeRect id={id} selected={selected} type={node.type} state={node.data.state}>
-      <div style={{ padding: '10px 20px' }}>
-        {nodeLabel(node)}
-        <Handle
-          id="repository"
-          type={"target"}
-          position={Position.Left}
-          isConnectableStart={isConnectableRepository}
-          isConnectableEnd={isConnectableRepository}
-          isConnectable={isConnectableRepository}
-        />
-        <Handle
-          id="ports"
-          type={"source"}
-          position={Position.Top}
-          isConnectableStart={isConnectablePorts}
-          isConnectableEnd={isConnectablePorts}
-          isConnectable={isConnectablePorts}
-        />
-        <Handle
-          id="env_var"
-          type={"target"}
-          position={Position.Bottom}
-          isConnectableStart={true}
-          isConnectableEnd={true}
-          isConnectable={true}
-        />
-      </div>
-    </NodeRect>
-  );
+	const { id, selected } = node;
+	const isConnectablePorts = useMemo(() => nodeIsConnectable(node, "ports"), [node]);
+	const isConnectableRepository = useMemo(() => nodeIsConnectable(node, "repository"), [node]);
+	return (
+		<NodeRect id={id} selected={selected} type={node.type} state={node.data.state}>
+			<div style={{ padding: "10px 20px" }}>
+				{nodeLabel(node)}
+				<Handle
+					id="repository"
+					type={"target"}
+					position={Position.Left}
+					isConnectableStart={isConnectableRepository}
+					isConnectableEnd={isConnectableRepository}
+					isConnectable={isConnectableRepository}
+				/>
+				<Handle
+					id="ports"
+					type={"source"}
+					position={Position.Top}
+					isConnectableStart={isConnectablePorts}
+					isConnectableEnd={isConnectablePorts}
+					isConnectable={isConnectablePorts}
+				/>
+				<Handle
+					id="env_var"
+					type={"target"}
+					position={Position.Bottom}
+					isConnectableStart={true}
+					isConnectableEnd={true}
+					isConnectable={true}
+				/>
+			</div>
+		</NodeRect>
+	);
 }
 
 const schema = z.object({
-  name: z.string().min(1, "requried"),
-  type: z.enum(ServiceTypes),
+	name: z.string().min(1, "requried"),
+	type: z.enum(ServiceTypes),
 });
 
 const portSchema = z.object({
-  name: z.string().min(1, "required"),
-  value: z.coerce.number().gt(0, "can not be negative"),
+	name: z.string().min(1, "required"),
+	value: z.coerce.number().gt(0, "can not be negative"),
 });
 
 const sourceSchema = z.object({
-  id: z.string().min(1, "required"),
-  branch: z.string(),
-  rootDir: z.string(),
+	id: z.string().min(1, "required"),
+	branch: z.string(),
+	rootDir: z.string(),
 });
 
 export function NodeAppDetails({ id, data }: ServiceNode) {
-  const store = useStateStore();
-  const nodes = useNodes<AppNode>();
-  const form = useForm<z.infer<typeof schema>>({
-    resolver: zodResolver(schema),
-    mode: "onChange",
-    defaultValues: {
-      name: data.label,
-      type: data.type,
-    }
-  });
-  const portForm = useForm<z.infer<typeof portSchema>>({
-    resolver: zodResolver(portSchema),
-    mode: "onSubmit",
-    defaultValues: {
-      name: "",
-      value: 0,
-    }
-  });
-  const onSubmit = useCallback((values: z.infer<typeof portSchema>) => {
-    const portId = uuidv4();
-    store.updateNodeData<"app">(id, {
-      ports: (data.ports || []).concat({
-        id: portId,
-        name: values.name,
-        value: values.value,
-      }),
-      envVars: (data.envVars || []).concat({
-        id: uuidv4(),
-        source: null,
-        portId,
-        name: `DODO_PORT_${values.name.toUpperCase()}`,
-      }),
-    });
-    portForm.reset();
-  }, [id, data, portForm, store]);
-  useEffect(() => {
-    const sub = form.watch((value: DeepPartial<z.infer<typeof schema>>, { name, type }: { name?: keyof z.infer<typeof schema> | undefined, type?: EventType | undefined }) => {
-      console.log({ name, type });
-      if (type !== "change") {
-        return;
-      }
-      switch (name) {
-        case "name":
-          if (!value.name) {
-            break;
-          }
-          store.updateNodeData<"app">(id, {
-            label: value.name,
-          });
-          break;
-        case "type":
-          if (!value.type) {
-            break;
-          }
-          store.updateNodeData<"app">(id, {
-            type: value.type,
-          })
-          break;
-      }
-    });
-    return () => sub.unsubscribe();
-  }, [id, form, store]);
-  const focus = useCallback((field: ControllerRenderProps<z.infer<typeof schema>, FieldPath<z.infer<typeof schema>>>, name: string) => {
-    return (e: HTMLElement | null) => {
-      field.ref(e);
-      if (e != null && name === data.activeField) {
-        console.log(e);
-        e.focus();
-        store.updateNodeData(id, {
-          activeField: undefined,
-        });
-      }
-    }
-  }, [id, data, store]);
-  const [typeProps, setTypeProps] = useState({});
-  useEffect(() => {
-    if (data.activeField === "type") {
-      setTypeProps({
-        open: true,
-        onOpenChange: () => store.updateNodeData(id, { activeField: undefined }),
-      });
-    } else {
-      setTypeProps({});
-    }
-  }, [id, data, store, setTypeProps]);
-  const editAlias = useCallback((e: BoundEnvVar) => {
-    return () => {
-      store.updateNodeData(id, {
-        ...data,
-        envVars: data.envVars!.map((o) => {
-          if (o.id !== e.id) {
-            return o;
-          } else return {
-            ...o,
-            isEditting: true,
-          }
-        }),
-      });
-    };
-  }, [id, data, store]);
-  const saveAlias = useCallback((e: BoundEnvVar, value: string, store: AppState) => {
-    store.updateNodeData(id, {
-      ...data,
-      envVars: data.envVars!.map((o) => {
-        if (o.id !== e.id) {
-          return o;
-        }
-        if (value) {
-          return {
-            ...o,
-            isEditting: false,
-            alias: value.toUpperCase(),
-          }
-        }
-        console.log(o);
-        if ("alias" in o) {
-          const { alias: _, ...rest } = o;
-          console.log(rest);
-          return {
-            ...rest,
-            isEditting: false,
-          };
-        }
-        return {
-          ...o,
-          isEditting: false,
-        };
-      }),
-    });
-  }, [id, data]);
-  const saveAliasOnEnter = useCallback((e: BoundEnvVar) => {
-    return (event: KeyboardEvent<HTMLInputElement>) => {
-      if (event.key === "Enter") {
-        event.preventDefault();
-        saveAlias(e, event.currentTarget.value, store);
-      }
-    }
-  }, [store, saveAlias]);
-  const saveAliasOnBlur = useCallback((e: BoundEnvVar) => {
-    return (event: FocusEvent<HTMLInputElement>) => {
-      saveAlias(e, event.currentTarget.value, store);
-    }
-  }, [store, saveAlias]);
-  const removePort = useCallback((portId: string) => {
-    // TODO(gio): this is ugly
-    const tcpRemoved = new Set<string>();
-    console.log(store.edges);
-    store.setEdges(store.edges.filter((e) => {
-      if (e.source !== id || e.sourceHandle !== "ports") {
-        return true;
-      }
-      const tn = store.nodes.find((n) => n.id == e.target)!;
-      if (e.targetHandle === "https") {
-        const t = tn as GatewayHttpsNode;
-        if (t.data.https?.serviceId === id && t.data.https.portId === portId) {
-          return false;
-        }
-      }
-      if (e.targetHandle === "tcp") {
-        const t = tn as GatewayTCPNode;
-        if (tcpRemoved.has(t.id)) {
-          return true;
-        }
-        if (t.data.exposed.find((e) => e.serviceId === id && e.portId === portId)) {
-          tcpRemoved.add(t.id);
-          return false;
-        }
-      }
-      if (e.targetHandle === "env_var") {
-        if (tn && (tn.data.envVars || []).find((ev) => ev.source === id && "portId" in ev && ev.portId === portId)) {
-          return false;
-        }
-      }
-      return true;
-    }));
-    store.nodes.filter((n) => n.type === "gateway-https" && n.data.https && n.data.https.serviceId === id && n.data.https.portId === portId).forEach((n) => {
-      store.updateNodeData<"gateway-https">(n.id, {
-        https: undefined,
-      });
-    });
-    store.nodes.filter((n) => n.type === "gateway-tcp").forEach((n) => {
-      const filtered = n.data.exposed.filter((e) => {
-        if (e.serviceId === id && e.portId === portId) {
-          return false;
-        } else {
-          return true;
-        }
-      })
-      if (filtered.length != n.data.exposed.length) {
-        store.updateNodeData<"gateway-tcp">(n.id, {
-          exposed: filtered,
-        });
-      }
-    });
-    store.nodes.filter((n) => n.type === "app" && n.data.envVars).forEach((n) => {
-      store.updateNodeData<"app">(n.id, {
-        envVars: n.data.envVars.filter((ev) => {
-          if (ev.source === id && "portId" in ev && ev.portId === portId) {
-            return false;
-          }
-          return true;
-        })
-      });
-    });
-    store.updateNodeData<"app">(id, {
-      ports: (data.ports || []).filter((p) => p.id !== portId),
-      envVars: (data.envVars || []).filter((ev) => !(ev.source === null && "portId" in ev && ev.portId === portId)),
-    });
-  }, [id, data, store]);
-  const setPreBuildCommands = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
-    store.updateNodeData<"app">(id, {
-      preBuildCommands: e.currentTarget.value,
-    });
-  }, [id, store]);
+	const store = useStateStore();
+	const nodes = useNodes<AppNode>();
+	const form = useForm<z.infer<typeof schema>>({
+		resolver: zodResolver(schema),
+		mode: "onChange",
+		defaultValues: {
+			name: data.label,
+			type: data.type,
+		},
+	});
+	const portForm = useForm<z.infer<typeof portSchema>>({
+		resolver: zodResolver(portSchema),
+		mode: "onSubmit",
+		defaultValues: {
+			name: "",
+			value: 0,
+		},
+	});
+	const onSubmit = useCallback(
+		(values: z.infer<typeof portSchema>) => {
+			const portId = uuidv4();
+			store.updateNodeData<"app">(id, {
+				ports: (data.ports || []).concat({
+					id: portId,
+					name: values.name,
+					value: values.value,
+				}),
+				envVars: (data.envVars || []).concat({
+					id: uuidv4(),
+					source: null,
+					portId,
+					name: `DODO_PORT_${values.name.toUpperCase()}`,
+				}),
+			});
+			portForm.reset();
+		},
+		[id, data, portForm, store],
+	);
+	useEffect(() => {
+		const sub = form.watch(
+			(
+				value: DeepPartial<z.infer<typeof schema>>,
+				{ name, type }: { name?: keyof z.infer<typeof schema> | undefined; type?: EventType | undefined },
+			) => {
+				console.log({ name, type });
+				if (type !== "change") {
+					return;
+				}
+				switch (name) {
+					case "name":
+						if (!value.name) {
+							break;
+						}
+						store.updateNodeData<"app">(id, {
+							label: value.name,
+						});
+						break;
+					case "type":
+						if (!value.type) {
+							break;
+						}
+						store.updateNodeData<"app">(id, {
+							type: value.type,
+						});
+						break;
+				}
+			},
+		);
+		return () => sub.unsubscribe();
+	}, [id, form, store]);
+	const focus = useCallback(
+		(field: ControllerRenderProps<z.infer<typeof schema>, FieldPath<z.infer<typeof schema>>>, name: string) => {
+			return (e: HTMLElement | null) => {
+				field.ref(e);
+				if (e != null && name === data.activeField) {
+					console.log(e);
+					e.focus();
+					store.updateNodeData(id, {
+						activeField: undefined,
+					});
+				}
+			};
+		},
+		[id, data, store],
+	);
+	const [typeProps, setTypeProps] = useState({});
+	useEffect(() => {
+		if (data.activeField === "type") {
+			setTypeProps({
+				open: true,
+				onOpenChange: () => store.updateNodeData(id, { activeField: undefined }),
+			});
+		} else {
+			setTypeProps({});
+		}
+	}, [id, data, store, setTypeProps]);
+	const editAlias = useCallback(
+		(e: BoundEnvVar) => {
+			return () => {
+				store.updateNodeData(id, {
+					...data,
+					envVars: data.envVars!.map((o) => {
+						if (o.id !== e.id) {
+							return o;
+						} else
+							return {
+								...o,
+								isEditting: true,
+							};
+					}),
+				});
+			};
+		},
+		[id, data, store],
+	);
+	const saveAlias = useCallback(
+		(e: BoundEnvVar, value: string, store: AppState) => {
+			store.updateNodeData(id, {
+				...data,
+				envVars: data.envVars!.map((o) => {
+					if (o.id !== e.id) {
+						return o;
+					}
+					if (value) {
+						return {
+							...o,
+							isEditting: false,
+							alias: value.toUpperCase(),
+						};
+					}
+					console.log(o);
+					if ("alias" in o) {
+						const { alias: _, ...rest } = o;
+						console.log(rest);
+						return {
+							...rest,
+							isEditting: false,
+						};
+					}
+					return {
+						...o,
+						isEditting: false,
+					};
+				}),
+			});
+		},
+		[id, data],
+	);
+	const saveAliasOnEnter = useCallback(
+		(e: BoundEnvVar) => {
+			return (event: KeyboardEvent<HTMLInputElement>) => {
+				if (event.key === "Enter") {
+					event.preventDefault();
+					saveAlias(e, event.currentTarget.value, store);
+				}
+			};
+		},
+		[store, saveAlias],
+	);
+	const saveAliasOnBlur = useCallback(
+		(e: BoundEnvVar) => {
+			return (event: FocusEvent<HTMLInputElement>) => {
+				saveAlias(e, event.currentTarget.value, store);
+			};
+		},
+		[store, saveAlias],
+	);
+	const removePort = useCallback(
+		(portId: string) => {
+			// TODO(gio): this is ugly
+			const tcpRemoved = new Set<string>();
+			console.log(store.edges);
+			store.setEdges(
+				store.edges.filter((e) => {
+					if (e.source !== id || e.sourceHandle !== "ports") {
+						return true;
+					}
+					const tn = store.nodes.find((n) => n.id == e.target)!;
+					if (e.targetHandle === "https") {
+						const t = tn as GatewayHttpsNode;
+						if (t.data.https?.serviceId === id && t.data.https.portId === portId) {
+							return false;
+						}
+					}
+					if (e.targetHandle === "tcp") {
+						const t = tn as GatewayTCPNode;
+						if (tcpRemoved.has(t.id)) {
+							return true;
+						}
+						if (t.data.exposed.find((e) => e.serviceId === id && e.portId === portId)) {
+							tcpRemoved.add(t.id);
+							return false;
+						}
+					}
+					if (e.targetHandle === "env_var") {
+						if (
+							tn &&
+							(tn.data.envVars || []).find(
+								(ev) => ev.source === id && "portId" in ev && ev.portId === portId,
+							)
+						) {
+							return false;
+						}
+					}
+					return true;
+				}),
+			);
+			store.nodes
+				.filter(
+					(n) =>
+						n.type === "gateway-https" &&
+						n.data.https &&
+						n.data.https.serviceId === id &&
+						n.data.https.portId === portId,
+				)
+				.forEach((n) => {
+					store.updateNodeData<"gateway-https">(n.id, {
+						https: undefined,
+					});
+				});
+			store.nodes
+				.filter((n) => n.type === "gateway-tcp")
+				.forEach((n) => {
+					const filtered = n.data.exposed.filter((e) => {
+						if (e.serviceId === id && e.portId === portId) {
+							return false;
+						} else {
+							return true;
+						}
+					});
+					if (filtered.length != n.data.exposed.length) {
+						store.updateNodeData<"gateway-tcp">(n.id, {
+							exposed: filtered,
+						});
+					}
+				});
+			store.nodes
+				.filter((n) => n.type === "app" && n.data.envVars)
+				.forEach((n) => {
+					store.updateNodeData<"app">(n.id, {
+						envVars: n.data.envVars.filter((ev) => {
+							if (ev.source === id && "portId" in ev && ev.portId === portId) {
+								return false;
+							}
+							return true;
+						}),
+					});
+				});
+			store.updateNodeData<"app">(id, {
+				ports: (data.ports || []).filter((p) => p.id !== portId),
+				envVars: (data.envVars || []).filter(
+					(ev) => !(ev.source === null && "portId" in ev && ev.portId === portId),
+				),
+			});
+		},
+		[id, data, store],
+	);
+	const setPreBuildCommands = useCallback(
+		(e: React.ChangeEvent<HTMLTextAreaElement>) => {
+			store.updateNodeData<"app">(id, {
+				preBuildCommands: e.currentTarget.value,
+			});
+		},
+		[id, store],
+	);
 
-  const sourceForm = useForm<z.infer<typeof sourceSchema>>({
-    resolver: zodResolver(sourceSchema),
-    mode: "onChange",
-    defaultValues: {
-      id: data?.repository?.id,
-      branch: data.repository && "branch" in data.repository ? data.repository.branch : undefined,
-      rootDir: data.repository && "rootDir" in data.repository ? data.repository.rootDir : undefined,
-    },
-  });
-  useEffect(() => {
-    const sub = sourceForm.watch((value: DeepPartial<z.infer<typeof sourceSchema>>, { name }: { name?: keyof z.infer<typeof sourceSchema> | undefined, type?: EventType | undefined }) => {
-      console.log(value);
-      if (name === "id") {
-        let edges = store.edges;
-        if (data?.repository?.id !== undefined) {
-          edges = edges.filter((e) => {
-            if (e.target === id && e.targetHandle === "repository" && e.source === data.repository.id) {
-              return false;
-            } else {
-              return true;
-            }
-          });
-        }
-        if (value.id !== undefined) {
-          edges = edges.concat({
-            id: uuidv4(),
-            source: value.id,
-            sourceHandle: "repository",
-            target: id,
-            targetHandle: "repository",
-          });
-        }
-        store.setEdges(edges);
-        store.updateNodeData<"app">(id, {
-          repository: {
-            id: value.id,
-          },
-        });
-      } else if (name === "branch") {
-        store.updateNodeData<"app">(id, {
-          repository: {
-            ...data?.repository,
-            branch: value.branch,
-          },
-        });
-      } else if (name === "rootDir") {
-        store.updateNodeData<"app">(id, {
-          repository: {
-            ...data?.repository,
-            rootDir: value.rootDir,
-          },
-        });
-      }
-    });
-    return () => sub.unsubscribe();
-  }, [id, data, sourceForm, store]);
+	const sourceForm = useForm<z.infer<typeof sourceSchema>>({
+		resolver: zodResolver(sourceSchema),
+		mode: "onChange",
+		defaultValues: {
+			id: data?.repository?.id,
+			branch: data.repository && "branch" in data.repository ? data.repository.branch : undefined,
+			rootDir: data.repository && "rootDir" in data.repository ? data.repository.rootDir : undefined,
+		},
+	});
+	useEffect(() => {
+		const sub = sourceForm.watch(
+			(
+				value: DeepPartial<z.infer<typeof sourceSchema>>,
+				{ name }: { name?: keyof z.infer<typeof sourceSchema> | undefined; type?: EventType | undefined },
+			) => {
+				console.log(value);
+				if (name === "id") {
+					let edges = store.edges;
+					if (data?.repository?.id !== undefined) {
+						edges = edges.filter((e) => {
+							if (e.target === id && e.targetHandle === "repository" && e.source === data.repository.id) {
+								return false;
+							} else {
+								return true;
+							}
+						});
+					}
+					if (value.id !== undefined) {
+						edges = edges.concat({
+							id: uuidv4(),
+							source: value.id,
+							sourceHandle: "repository",
+							target: id,
+							targetHandle: "repository",
+						});
+					}
+					store.setEdges(edges);
+					store.updateNodeData<"app">(id, {
+						repository: {
+							id: value.id,
+						},
+					});
+				} else if (name === "branch") {
+					store.updateNodeData<"app">(id, {
+						repository: {
+							...data?.repository,
+							branch: value.branch,
+						},
+					});
+				} else if (name === "rootDir") {
+					store.updateNodeData<"app">(id, {
+						repository: {
+							...data?.repository,
+							rootDir: value.rootDir,
+						},
+					});
+				}
+			},
+		);
+		return () => sub.unsubscribe();
+	}, [id, data, sourceForm, store]);
 
-  return (
-    <>
-      <Form {...form}>
-        <form>
-          <FormField
-            control={form.control}
-            name="name"
-            render={({ field }) => (
-              <FormItem>
-                <FormControl>
-                  <Input placeholder="name" className="border border-black" {...field} ref={focus(field, "name")} />
-                </FormControl>
-                <FormMessage />
-              </FormItem>
-            )}
-          />
-          <FormField
-            control={form.control}
-            name="type"
-            render={({ field }) => (
-              <FormItem>
-                <Select onValueChange={field.onChange} defaultValue={field.value} {...typeProps}>
-                  <FormControl>
-                    <SelectTrigger>
-                      <SelectValue placeholder="Runtime" />
-                    </SelectTrigger>
-                  </FormControl>
-                  <SelectContent>
-                    {ServiceTypes.map((t) => (
-                      <SelectItem key={t} value={t}>{t}</SelectItem>
-                    ))}
-                  </SelectContent>
-                </Select>
-                <FormMessage />
-              </FormItem>
-            )}
-          />
-        </form>
-      </Form>
-      Source
-      <Form {...sourceForm}>
-        <form className="space-y-2">
-          <FormField
-            control={sourceForm.control}
-            name="id"
-            render={({ field }) => (
-              <FormItem>
-                <Select onValueChange={field.onChange} defaultValue={field.value}>
-                  <FormControl>
-                    <SelectTrigger>
-                      <SelectValue placeholder="Repository" />
-                    </SelectTrigger>
-                  </FormControl>
-                  <SelectContent>
-                    {(nodes.filter((n) => n.type === "github" && n.data.repository?.id !== undefined) as GithubNode[]).map((n) => (
-                      <SelectItem key={n.id} value={n.id}>{`${n.data.repository?.sshURL}`}</SelectItem>
-                    ))}
-                  </SelectContent>
-                </Select>
-                <FormMessage />
-              </FormItem>
-            )}
-          />
-          <FormField
-            control={sourceForm.control}
-            name="branch"
-            render={({ field }) => (
-              <FormItem>
-                <FormControl>
-                  <Input placeholder="master" className="border border-black" {...field} />
-                </FormControl>
-                <FormMessage />
-              </FormItem>
-            )}
-          />
-          <FormField
-            control={sourceForm.control}
-            name="rootDir"
-            render={({ field }) => (
-              <FormItem>
-                <FormControl>
-                  <Input placeholder="/" className="border border-black" {...field} />
-                </FormControl>
-                <FormMessage />
-              </FormItem>
-            )}
-          />
-        </form>
-      </Form>
-      Ports
-      <ul>
-        {data && data.ports && data.ports.map((p) => (<li key={p.id}><Button size={"icon"} variant={"ghost"} onClick={() => removePort(p.id)}><XIcon /></Button> {p.name} - {p.value}</li>))}
-      </ul>
-      <Form {...portForm}>
-        <form className="flex flex-row space-x-1" onSubmit={portForm.handleSubmit(onSubmit)}>
-          <FormField
-            control={portForm.control}
-            name="name"
-            render={({ field }) => (
-              <FormItem>
-                <FormControl>
-                  <Input placeholder="name" className="border border-black" {...field} />
-                </FormControl>
-                <FormMessage />
-              </FormItem>
-            )}
-          />
-          <FormField
-            control={portForm.control}
-            name="value"
-            render={({ field }) => (
-              <FormItem>
-                <FormControl>
-                  <Input placeholder="value" className="border border-black" {...field} />
-                </FormControl>
-                <FormMessage />
-              </FormItem>
-            )}
-          />
-          <Button type="submit">Add Port</Button>
-        </form>
-      </Form>
-      Env Vars
-      <ul>
-        {data && data.envVars && data.envVars.map((v) => {
-          if ("name" in v) {
-            const value = "alias" in v ? v.alias : v.name;
-            if (v.isEditting) {
-              return (<li key={v.id}><Input type="text" className="border border-black" defaultValue={value} onKeyUp={saveAliasOnEnter(v)} onBlur={saveAliasOnBlur(v)} autoFocus={true} /></li>);
-            }
-            return (
-              <li key={v.id} onClick={editAlias(v)}>
-                <TooltipProvider>
-                  <Tooltip>
-                    <TooltipTrigger>
-                      <Button size={"icon"} variant={"ghost"}><PencilIcon /></Button>
-                      {value}
-                    </TooltipTrigger>
-                    <TooltipContent>
-                      {v.name}
-                    </TooltipContent>
-                  </Tooltip>
-                </TooltipProvider>
-              </li>
-            );
-          }
-        })}
-      </ul>
-      Pre-Build Commands
-      <Textarea placeholder="new line separated list of commands to run before running the service" value={data.preBuildCommands} onChange={setPreBuildCommands} />
-    </>);
-}
\ No newline at end of file
+	return (
+		<>
+			<Form {...form}>
+				<form>
+					<FormField
+						control={form.control}
+						name="name"
+						render={({ field }) => (
+							<FormItem>
+								<FormControl>
+									<Input
+										placeholder="name"
+										className="border border-black"
+										{...field}
+										ref={focus(field, "name")}
+									/>
+								</FormControl>
+								<FormMessage />
+							</FormItem>
+						)}
+					/>
+					<FormField
+						control={form.control}
+						name="type"
+						render={({ field }) => (
+							<FormItem>
+								<Select onValueChange={field.onChange} defaultValue={field.value} {...typeProps}>
+									<FormControl>
+										<SelectTrigger>
+											<SelectValue placeholder="Runtime" />
+										</SelectTrigger>
+									</FormControl>
+									<SelectContent>
+										{ServiceTypes.map((t) => (
+											<SelectItem key={t} value={t}>
+												{t}
+											</SelectItem>
+										))}
+									</SelectContent>
+								</Select>
+								<FormMessage />
+							</FormItem>
+						)}
+					/>
+				</form>
+			</Form>
+			Source
+			<Form {...sourceForm}>
+				<form className="space-y-2">
+					<FormField
+						control={sourceForm.control}
+						name="id"
+						render={({ field }) => (
+							<FormItem>
+								<Select onValueChange={field.onChange} defaultValue={field.value}>
+									<FormControl>
+										<SelectTrigger>
+											<SelectValue placeholder="Repository" />
+										</SelectTrigger>
+									</FormControl>
+									<SelectContent>
+										{(
+											nodes.filter(
+												(n) => n.type === "github" && n.data.repository?.id !== undefined,
+											) as GithubNode[]
+										).map((n) => (
+											<SelectItem
+												key={n.id}
+												value={n.id}
+											>{`${n.data.repository?.sshURL}`}</SelectItem>
+										))}
+									</SelectContent>
+								</Select>
+								<FormMessage />
+							</FormItem>
+						)}
+					/>
+					<FormField
+						control={sourceForm.control}
+						name="branch"
+						render={({ field }) => (
+							<FormItem>
+								<FormControl>
+									<Input placeholder="master" className="border border-black" {...field} />
+								</FormControl>
+								<FormMessage />
+							</FormItem>
+						)}
+					/>
+					<FormField
+						control={sourceForm.control}
+						name="rootDir"
+						render={({ field }) => (
+							<FormItem>
+								<FormControl>
+									<Input placeholder="/" className="border border-black" {...field} />
+								</FormControl>
+								<FormMessage />
+							</FormItem>
+						)}
+					/>
+				</form>
+			</Form>
+			Ports
+			<ul>
+				{data &&
+					data.ports &&
+					data.ports.map((p) => (
+						<li key={p.id}>
+							<Button size={"icon"} variant={"ghost"} onClick={() => removePort(p.id)}>
+								<XIcon />
+							</Button>{" "}
+							{p.name} - {p.value}
+						</li>
+					))}
+			</ul>
+			<Form {...portForm}>
+				<form className="flex flex-row space-x-1" onSubmit={portForm.handleSubmit(onSubmit)}>
+					<FormField
+						control={portForm.control}
+						name="name"
+						render={({ field }) => (
+							<FormItem>
+								<FormControl>
+									<Input placeholder="name" className="border border-black" {...field} />
+								</FormControl>
+								<FormMessage />
+							</FormItem>
+						)}
+					/>
+					<FormField
+						control={portForm.control}
+						name="value"
+						render={({ field }) => (
+							<FormItem>
+								<FormControl>
+									<Input placeholder="value" className="border border-black" {...field} />
+								</FormControl>
+								<FormMessage />
+							</FormItem>
+						)}
+					/>
+					<Button type="submit">Add Port</Button>
+				</form>
+			</Form>
+			Env Vars
+			<ul>
+				{data &&
+					data.envVars &&
+					data.envVars.map((v) => {
+						if ("name" in v) {
+							const value = "alias" in v ? v.alias : v.name;
+							if (v.isEditting) {
+								return (
+									<li key={v.id}>
+										<Input
+											type="text"
+											className="border border-black"
+											defaultValue={value}
+											onKeyUp={saveAliasOnEnter(v)}
+											onBlur={saveAliasOnBlur(v)}
+											autoFocus={true}
+										/>
+									</li>
+								);
+							}
+							return (
+								<li key={v.id} onClick={editAlias(v)}>
+									<TooltipProvider>
+										<Tooltip>
+											<TooltipTrigger>
+												<Button size={"icon"} variant={"ghost"}>
+													<PencilIcon />
+												</Button>
+												{value}
+											</TooltipTrigger>
+											<TooltipContent>{v.name}</TooltipContent>
+										</Tooltip>
+									</TooltipProvider>
+								</li>
+							);
+						}
+					})}
+			</ul>
+			Pre-Build Commands
+			<Textarea
+				placeholder="new line separated list of commands to run before running the service"
+				value={data.preBuildCommands}
+				onChange={setPreBuildCommands}
+			/>
+		</>
+	);
+}