diff --git a/apps/canvas/front/src/components/node-app.tsx b/apps/canvas/front/src/components/node-app.tsx
index 7516d19..71fc358 100644
--- a/apps/canvas/front/src/components/node-app.tsx
+++ b/apps/canvas/front/src/components/node-app.tsx
@@ -13,21 +13,26 @@
 	AppNode,
 	GithubNode,
 	useEnv,
+	useGithubRepositories,
 } 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 { useForm, EventType, DeepPartial } from "react-hook-form";
 import { zodResolver } from "@hookform/resolvers/zod";
 import { Form, FormControl, FormField, FormItem, FormMessage } from "./ui/form";
 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";
 import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./ui/tooltip";
 import { Textarea } from "./ui/textarea";
 import { Input } from "./ui/input";
-import { Checkbox } from "./ui/checkbox";
+import { Switch } from "./ui/switch";
 import { Label } from "./ui/label";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs";
+import { Code, Container, Network, Pencil, Variable } from "lucide-react";
+import { Icon } from "./icon";
+import { Badge } from "./ui/badge";
+import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "./ui/accordion";
 
 export function NodeApp(node: ServiceNode) {
 	const { id, selected } = node;
@@ -71,11 +76,6 @@
 	type: z.enum(ServiceTypes),
 });
 
-const portSchema = z.object({
-	name: z.string().min(1, "required"),
-	value: z.coerce.number().gt(0, "must be positive").lte(65535, "must be less than 65535"),
-});
-
 const sourceSchema = z.object({
 	id: z.string().min(1, "required"),
 	branch: z.string(),
@@ -91,10 +91,124 @@
 	subdomain: z.string().min(1, "required"),
 });
 
-export function NodeAppDetails({ id, data, disabled }: ServiceNode & { disabled?: boolean }) {
+export function NodeAppDetails({ node, disabled }: { node: ServiceNode; disabled?: boolean }) {
+	const { data } = node;
+	return (
+		<>
+			<Name node={node} disabled={disabled} />
+			<Tabs defaultValue="runtime">
+				<TabsList className="w-full flex flex-row justify-between">
+					<TabsTrigger value="runtime">
+						<TooltipProvider>
+							<Tooltip>
+								<TooltipTrigger>
+									<Container />
+								</TooltipTrigger>
+								<TooltipContent>Runtime</TooltipContent>
+							</Tooltip>
+						</TooltipProvider>
+					</TabsTrigger>
+					<TabsTrigger value="ports">
+						<TooltipProvider>
+							<Tooltip>
+								<TooltipTrigger className="flex flex-row gap-1 items-center">
+									<Network />
+								</TooltipTrigger>
+								<TooltipContent>
+									Ports{" "}
+									<Badge variant="secondary" className="rounded-full">
+										{data.ports?.length ?? 0}
+									</Badge>
+								</TooltipContent>
+							</Tooltip>
+						</TooltipProvider>
+					</TabsTrigger>
+					<TabsTrigger value="vars">
+						<TooltipProvider>
+							<Tooltip>
+								<TooltipTrigger className="flex flex-row gap-1 items-center">
+									<Variable />
+								</TooltipTrigger>
+								<TooltipContent>
+									Variables{" "}
+									<Badge variant="secondary" className="rounded-full">
+										{data.envVars?.length ?? 0}
+									</Badge>
+								</TooltipContent>
+							</Tooltip>
+						</TooltipProvider>
+					</TabsTrigger>
+					<TabsTrigger value="dev">
+						<TooltipProvider>
+							<Tooltip>
+								<TooltipTrigger className="flex flex-row gap-1 items-center">
+									<Code />
+								</TooltipTrigger>
+								<TooltipContent>Dev</TooltipContent>
+							</Tooltip>
+						</TooltipProvider>
+					</TabsTrigger>
+				</TabsList>
+				<TabsContent value="runtime">
+					<Runtime node={node} disabled={disabled} />
+				</TabsContent>
+				<TabsContent value="ports">
+					<Ports node={node} disabled={disabled} />
+				</TabsContent>
+				<TabsContent value="vars">
+					<EnvVars node={node} disabled={disabled} />
+				</TabsContent>
+				<TabsContent value="dev">
+					<Dev node={node} disabled={disabled} />
+				</TabsContent>
+			</Tabs>
+		</>
+	);
+}
+
+function Name({ node, disabled }: { node: ServiceNode; disabled?: boolean }): React.ReactNode {
+	const { id, data } = node;
 	const store = useStateStore();
-	const nodes = useNodes<AppNode>();
-	const env = useEnv();
+	const [isEditing, setIsEditing] = useState(false);
+	useEffect(() => {
+		if (data.label === "" && !disabled) {
+			setIsEditing(true);
+		}
+	}, [data.label, disabled]);
+	return (
+		<div className="flex flex-row gap-1 items-center">
+			<Icon type="app" />
+			{isEditing ? (
+				<Input
+					placeholder="Name"
+					value={data.label}
+					onChange={(e) => store.updateNodeData(id, { label: e.target.value })}
+					onBlur={() => {
+						if (data.label !== "") {
+							setIsEditing(false);
+						}
+					}}
+					autoFocus={true}
+				/>
+			) : (
+				<h3
+					className="text-lg font-bold cursor-text select-none hover:outline-solid hover:outline-2 hover:outline-gray-200"
+					onClick={() => {
+						if (!disabled) {
+							setIsEditing(true);
+						}
+					}}
+				>
+					{data.label}
+				</h3>
+			)}
+		</div>
+	);
+}
+
+function Runtime({ node, disabled }: { node: ServiceNode; disabled?: boolean }): React.ReactNode {
+	const { id, data } = node;
+	const store = useStateStore();
 	const form = useForm<z.infer<typeof schema>>({
 		resolver: zodResolver(schema),
 		mode: "onChange",
@@ -103,41 +217,12 @@
 			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.toLowerCase(),
-					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;
 				}
@@ -163,21 +248,6 @@
 		);
 		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") {
@@ -189,82 +259,88 @@
 			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,
-					};
-				}),
+	const setPreBuildCommands = useCallback(
+		(e: React.ChangeEvent<HTMLTextAreaElement>) => {
+			store.updateNodeData<"app">(id, {
+				preBuildCommands: e.currentTarget.value,
 			});
 		},
-		[id, data],
+		[id, store],
 	);
-	const saveAliasOnEnter = useCallback(
-		(e: BoundEnvVar) => {
-			return (event: KeyboardEvent<HTMLInputElement>) => {
-				if (event.key === "Enter") {
-					event.preventDefault();
-					saveAlias(e, event.currentTarget.value, store);
-				}
-			};
-		},
-		[store, saveAlias],
+	return (
+		<>
+			<SourceRepo node={node} disabled={disabled} />
+			<Form {...form}>
+				<form className="space-y-2">
+					<Label>Container Image</Label>
+					<FormField
+						control={form.control}
+						name="type"
+						render={({ field }) => (
+							<FormItem>
+								<Select
+									onValueChange={field.onChange}
+									value={field.value || ""}
+									{...typeProps}
+									disabled={disabled}
+								>
+									<FormControl>
+										<SelectTrigger>
+											<SelectValue />
+										</SelectTrigger>
+									</FormControl>
+									<SelectContent>
+										{ServiceTypes.map((t) => (
+											<SelectItem key={t} value={t}>
+												{t}
+											</SelectItem>
+										))}
+									</SelectContent>
+								</Select>
+								<FormMessage />
+							</FormItem>
+						)}
+					/>
+				</form>
+			</Form>
+			<Label>Pre-Build Commands</Label>
+			<Textarea
+				placeholder="new line separated list of commands to run before running the service"
+				value={data.preBuildCommands}
+				onChange={setPreBuildCommands}
+				disabled={disabled}
+			/>
+		</>
 	);
-	const saveAliasOnBlur = useCallback(
-		(e: BoundEnvVar) => {
-			return (event: FocusEvent<HTMLInputElement>) => {
-				saveAlias(e, event.currentTarget.value, store);
-			};
-		},
-		[store, saveAlias],
-	);
+}
+
+function Ports({ node, disabled }: { node: ServiceNode; disabled?: boolean }): React.ReactNode {
+	const { id, data } = node;
+	const store = useStateStore();
+	const [name, setName] = useState("");
+	const [value, setValue] = useState("");
+	const onSubmit = useCallback(() => {
+		const portId = uuidv4();
+		store.updateNodeData<"app">(id, {
+			ports: (data.ports || []).concat({
+				id: portId,
+				name: name.toUpperCase(),
+				value: Number(value),
+			}),
+			envVars: (data.envVars || []).concat({
+				id: uuidv4(),
+				source: null,
+				portId,
+				name: `DODO_PORT_${name.toUpperCase()}`,
+			}),
+		});
+		setName("");
+		setValue("");
+	}, [id, data, store, name, value, setName, setValue]);
 	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") {
@@ -350,76 +426,174 @@
 		},
 		[id, data, store],
 	);
-	const setPreBuildCommands = useCallback(
-		(e: React.ChangeEvent<HTMLTextAreaElement>) => {
-			store.updateNodeData<"app">(id, {
-				preBuildCommands: e.currentTarget.value,
+	return (
+		<div className="flex flex-col gap-1">
+			<div className="grid grid-cols-[1fr_1fr_auto] gap-1">
+				{data &&
+					data.ports &&
+					data.ports.map((p) => (
+						<>
+							<div className="flex items-center px-3">{p.name.toUpperCase()}</div>
+							<div className="flex items-center px-3">{p.value}</div>
+							<div className="flex items-center">
+								<Button
+									variant="destructive"
+									className="w-full"
+									onClick={() => removePort(p.id)}
+									disabled={disabled}
+								>
+									Remove
+								</Button>
+							</div>
+						</>
+					))}
+				<div>
+					<Input
+						placeholder="name"
+						className="uppercase w-0 min-w-full"
+						disabled={disabled}
+						value={name}
+						onChange={(e) => setName(e.target.value)}
+					/>
+				</div>
+				<div>
+					<Input
+						placeholder="0"
+						className="w-0 min-w-full"
+						disabled={disabled}
+						value={value}
+						onChange={(e) => setValue(e.target.value)}
+					/>
+				</div>
+				<div>
+					<Button type="submit" className="w-full" disabled={disabled} onClick={onSubmit}>
+						Add
+					</Button>
+				</div>
+			</div>
+		</div>
+	);
+}
+
+function EnvVars({ node, disabled }: { node: ServiceNode; disabled?: boolean }): React.ReactNode {
+	const { id, data } = node;
+	const store = useStateStore();
+	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(),
+						};
+					}
+					if ("alias" in o) {
+						const { alias: _, ...rest } = o;
+						return {
+							...rest,
+							isEditting: false,
+						};
+					}
+					return {
+						...o,
+						isEditting: false,
+					};
+				}),
 			});
 		},
-		[id, store],
+		[id, data],
 	);
-
-	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,
-						},
-					});
+	const saveAliasOnEnter = useCallback(
+		(e: BoundEnvVar) => {
+			return (event: KeyboardEvent<HTMLInputElement>) => {
+				if (event.key === "Enter") {
+					event.preventDefault();
+					saveAlias(e, event.currentTarget.value, store);
 				}
-			},
-		);
-		return () => sub.unsubscribe();
-	}, [id, data, sourceForm, store]);
+			};
+		},
+		[store, saveAlias],
+	);
+	const saveAliasOnBlur = useCallback(
+		(e: BoundEnvVar) => {
+			return (event: FocusEvent<HTMLInputElement>) => {
+				saveAlias(e, event.currentTarget.value, store);
+			};
+		},
+		[store, saveAlias],
+	);
+	return (
+		<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="uppercase"
+										defaultValue={value}
+										onKeyUp={saveAliasOnEnter(v)}
+										onBlur={saveAliasOnBlur(v)}
+										autoFocus={true}
+										disabled={disabled}
+									/>
+								</li>
+							);
+						}
+						return (
+							<li key={v.id} onClick={editAlias(v)}>
+								<TooltipProvider>
+									<Tooltip>
+										<TooltipTrigger className="w-full">
+											<div className="w-full flex flex-row items-center gap-1 cursor-text">
+												<Pencil className="w-4 h-4" />
+												<div className="uppercase">{value}</div>
+											</div>
+										</TooltipTrigger>
+										<TooltipContent>{v.name}</TooltipContent>
+									</Tooltip>
+								</TooltipProvider>
+							</li>
+						);
+					}
+				})}
+		</ul>
+	);
+}
+
+function Dev({ node, disabled }: { node: ServiceNode; disabled?: boolean }): React.ReactNode {
+	const { id, data } = node;
+	const env = useEnv();
+	const store = useStateStore();
 	const devForm = useForm<z.infer<typeof devSchema>>({
 		resolver: zodResolver(devSchema),
 		mode: "onChange",
@@ -627,215 +801,6 @@
 	}, [id, data, exposeForm, store]);
 	return (
 		<>
-			<Form {...exposeForm}>
-				<form className="space-y-2">
-					<FormField
-						control={form.control}
-						name="name"
-						render={({ field }) => (
-							<FormItem>
-								<FormControl>
-									<Input
-										placeholder="name"
-										className="lowercase"
-										{...field}
-										ref={focus(field, "name")}
-										disabled={disabled}
-									/>
-								</FormControl>
-								<FormMessage />
-							</FormItem>
-						)}
-					/>
-					<FormField
-						control={form.control}
-						name="type"
-						render={({ field }) => (
-							<FormItem>
-								<Select
-									onValueChange={field.onChange}
-									defaultValue={field.value}
-									{...typeProps}
-									disabled={disabled}
-								>
-									<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} disabled={disabled}>
-									<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?.fullName}`}</SelectItem>
-										))}
-									</SelectContent>
-								</Select>
-								<FormMessage />
-							</FormItem>
-						)}
-					/>
-					<FormField
-						control={sourceForm.control}
-						name="branch"
-						render={({ field }) => (
-							<FormItem>
-								<FormControl>
-									<Input placeholder="master" className="lowercase" {...field} disabled={disabled} />
-								</FormControl>
-								<FormMessage />
-							</FormItem>
-						)}
-					/>
-					<FormField
-						control={sourceForm.control}
-						name="rootDir"
-						render={({ field }) => (
-							<FormItem>
-								<FormControl>
-									<Input placeholder="/" {...field} disabled={disabled} />
-								</FormControl>
-								<FormMessage />
-							</FormItem>
-						)}
-					/>
-				</form>
-			</Form>
-			Ports
-			<ul>
-				{data &&
-					data.ports &&
-					data.ports.map((p) => (
-						<li key={p.id} className="flex flex-row items-center gap-1">
-							<Button
-								size={"icon"}
-								variant={"ghost"}
-								onClick={() => removePort(p.id)}
-								className="w-4 h-4"
-								disabled={disabled}
-							>
-								<XIcon />
-							</Button>
-							<div>
-								{p.name} - {p.value}
-							</div>
-						</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="lowercase" {...field} disabled={disabled} />
-								</FormControl>
-								<FormMessage />
-							</FormItem>
-						)}
-					/>
-					<FormField
-						control={portForm.control}
-						name="value"
-						render={({ field }) => (
-							<FormItem>
-								<FormControl>
-									<Input placeholder="value" {...field} disabled={disabled} />
-								</FormControl>
-								<FormMessage />
-							</FormItem>
-						)}
-					/>
-					<Button type="submit" disabled={disabled}>
-						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="uppercase"
-											defaultValue={value}
-											onKeyUp={saveAliasOnEnter(v)}
-											onBlur={saveAliasOnBlur(v)}
-											autoFocus={true}
-											disabled={disabled}
-										/>
-									</li>
-								);
-							}
-							return (
-								<li key={v.id} onClick={editAlias(v)}>
-									<TooltipProvider>
-										<Tooltip>
-											<TooltipTrigger>
-												<div className="flex flex-row items-center gap-1">
-													<Button size={"icon"} variant={"ghost"} className="w-4 h-4">
-														<PencilIcon />
-													</Button>
-													<div>{value}</div>
-												</div>
-											</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}
-				disabled={disabled}
-			/>
-			Dev
 			<Form {...devForm}>
 				<form className="space-y-2">
 					<FormField
@@ -844,13 +809,13 @@
 						render={({ field }) => (
 							<FormItem>
 								<div className="flex flex-row gap-1 items-center">
-									<Checkbox
+									<Switch
 										id="devEnabled"
 										onCheckedChange={field.onChange}
 										checked={field.value}
 										disabled={disabled}
 									/>
-									<Label htmlFor="devEnabled">Enabled</Label>
+									<Label htmlFor="devEnabled">Dev VM</Label>
 								</div>
 								<FormMessage />
 							</FormItem>
@@ -861,6 +826,7 @@
 			{data.dev && data.dev.enabled && (
 				<Form {...exposeForm}>
 					<form className="space-y-2">
+						<Label>Network</Label>
 						<FormField
 							control={exposeForm.control}
 							name="network"
@@ -868,12 +834,12 @@
 								<FormItem>
 									<Select
 										onValueChange={field.onChange}
-										defaultValue={field.value}
+										value={field.value || ""}
 										disabled={disabled}
 									>
 										<FormControl>
 											<SelectTrigger>
-												<SelectValue placeholder="Network" />
+												<SelectValue />
 											</SelectTrigger>
 										</FormControl>
 										<SelectContent>
@@ -889,13 +855,14 @@
 								</FormItem>
 							)}
 						/>
+						<Label>Subdomain</Label>
 						<FormField
 							control={exposeForm.control}
 							name="subdomain"
 							render={({ field }) => (
 								<FormItem>
 									<FormControl>
-										<Input placeholder="subdomain" {...field} disabled={disabled} />
+										<Input {...field} disabled={disabled} />
 									</FormControl>
 									<FormMessage />
 								</FormItem>
@@ -907,3 +874,294 @@
 		</>
 	);
 }
+
+function SourceRepo({ node, disabled }: { node: ServiceNode; disabled?: boolean }): React.ReactNode {
+	const { id, data } = node;
+	const store = useStateStore();
+	const nodes = useNodes<AppNode>();
+	const repo = useMemo(() => {
+		return nodes
+			.filter((n): n is GithubNode => n.type === "github")
+			.find((n) => n.id === data.repository?.repoNodeId);
+	}, [nodes, data.repository?.repoNodeId]);
+	const repos = useGithubRepositories();
+	const sourceForm = useForm<z.infer<typeof sourceSchema>>({
+		resolver: zodResolver(sourceSchema),
+		mode: "onChange",
+		defaultValues: {
+			id: data?.repository?.id?.toString(),
+			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 },
+			) => {
+				if (name === "id") {
+					const newRepoId = value.id ? parseInt(value.id, 10) : undefined;
+					if (!newRepoId) return;
+
+					const oldGithubNodeId = data.repository?.repoNodeId;
+					const selectedRepo = repos.find((r) => r.id === newRepoId);
+
+					if (!selectedRepo) return;
+
+					// If a node for the selected repo already exists, connect to it.
+					const existingNodeForSelectedRepo = nodes
+						.filter((n): n is GithubNode => n.type === "github")
+						.find((n) => n.data.repository?.id === selectedRepo.id);
+
+					if (existingNodeForSelectedRepo) {
+						let { nodes, edges } = store;
+						if (oldGithubNodeId) {
+							edges = edges.filter(
+								(e) =>
+									!(
+										e.target === id &&
+										e.source === oldGithubNodeId &&
+										e.targetHandle === "repository"
+									),
+							);
+						}
+						edges = edges.concat({
+							id: uuidv4(),
+							source: existingNodeForSelectedRepo.id,
+							sourceHandle: "repository",
+							target: id,
+							targetHandle: "repository",
+						});
+						nodes = nodes.map((n) => {
+							if (n.id !== id) {
+								return n;
+							} else {
+								const sn = n as ServiceNode;
+								return {
+									...sn,
+									data: {
+										...sn.data,
+										repository: {
+											...sn.data.repository,
+											id: newRepoId,
+											repoNodeId: existingNodeForSelectedRepo.id,
+										},
+									},
+								};
+							}
+						});
+						if (oldGithubNodeId && oldGithubNodeId !== existingNodeForSelectedRepo.id) {
+							const isOldNodeStillUsed = edges.some(
+								(e) => e.source === oldGithubNodeId && e.sourceHandle === "repository",
+							);
+							if (!isOldNodeStillUsed) {
+								nodes = nodes.filter((n) => n.id !== oldGithubNodeId);
+							}
+						}
+						store.setNodes(nodes);
+						store.setEdges(edges);
+						return;
+					}
+
+					// No node for selected repo, decide whether to update old node or create a new one.
+					if (oldGithubNodeId) {
+						const isOldNodeShared =
+							store.edges.filter(
+								(e) =>
+									e.source === oldGithubNodeId && e.target !== id && e.sourceHandle === "repository",
+							).length > 0;
+
+						if (!isOldNodeShared) {
+							// Update old node
+							store.updateNodeData<"github">(oldGithubNodeId, {
+								repository: {
+									id: selectedRepo.id,
+									sshURL: selectedRepo.ssh_url,
+									fullName: selectedRepo.full_name,
+								},
+								label: selectedRepo.full_name,
+							});
+							store.updateNodeData<"app">(id, {
+								repository: {
+									...data.repository,
+									id: newRepoId,
+								},
+							});
+						} else {
+							// Create new node because old one is shared
+							const newGithubNodeId = uuidv4();
+							store.addNode({
+								id: newGithubNodeId,
+								type: "github",
+								data: {
+									repository: {
+										id: selectedRepo.id,
+										sshURL: selectedRepo.ssh_url,
+										fullName: selectedRepo.full_name,
+									},
+									label: selectedRepo.full_name,
+									envVars: [],
+									ports: [],
+								},
+							});
+
+							let edges = store.edges;
+							// remove old edge
+							edges = edges.filter(
+								(e) =>
+									!(
+										e.target === id &&
+										e.source === oldGithubNodeId &&
+										e.targetHandle === "repository"
+									),
+							);
+							// add new edge
+							edges = edges.concat({
+								id: uuidv4(),
+								source: newGithubNodeId,
+								sourceHandle: "repository",
+								target: id,
+								targetHandle: "repository",
+							});
+							store.setEdges(edges);
+							store.updateNodeData<"app">(id, {
+								repository: {
+									...data.repository,
+									id: newRepoId,
+									repoNodeId: newGithubNodeId,
+								},
+							});
+						}
+					} else {
+						// No old github node, so create a new one
+						const newGithubNodeId = uuidv4();
+						store.addNode({
+							id: newGithubNodeId,
+							type: "github",
+							data: {
+								repository: {
+									id: selectedRepo.id,
+									sshURL: selectedRepo.ssh_url,
+									fullName: selectedRepo.full_name,
+								},
+								label: selectedRepo.full_name,
+								envVars: [],
+								ports: [],
+							},
+						});
+						store.setEdges(
+							store.edges.concat({
+								id: uuidv4(),
+								source: newGithubNodeId,
+								sourceHandle: "repository",
+								target: id,
+								targetHandle: "repository",
+							}),
+						);
+						store.updateNodeData<"app">(id, {
+							repository: {
+								...data.repository,
+								id: newRepoId,
+								repoNodeId: newGithubNodeId,
+							},
+						});
+					}
+				} 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, nodes, repos]);
+	const [isExpanded, setIsExpanded] = useState(false);
+	// useEffect(() => {
+	// 	if (data.repository === undefined) {
+	// 		setIsExpanded(true);
+	// 	}
+	// }, [data.repository, setIsExpanded]);
+	console.log(data.repository, isExpanded, repo);
+	return (
+		<Accordion type="single" collapsible>
+			<AccordionItem value="repository" className="border-none">
+				<AccordionTrigger onClick={() => setIsExpanded(!isExpanded)}>
+					Source {!isExpanded && repo !== undefined && repo.data.repository?.fullName}
+				</AccordionTrigger>
+				<AccordionContent className="px-1">
+					<Form {...sourceForm}>
+						<form className="space-y-2">
+							<Label>Repository</Label>
+							<FormField
+								control={sourceForm.control}
+								name="id"
+								render={({ field }) => (
+									<FormItem>
+										<Select onValueChange={field.onChange} value={field.value} disabled={disabled}>
+											<FormControl>
+												<SelectTrigger>
+													<SelectValue />
+												</SelectTrigger>
+											</FormControl>
+											<SelectContent>
+												{repos.map((r) => (
+													<SelectItem
+														key={r.id}
+														value={r.id.toString()}
+													>{`${r.full_name}`}</SelectItem>
+												))}
+											</SelectContent>
+										</Select>
+										<FormMessage />
+									</FormItem>
+								)}
+							/>
+							<Label>Branch</Label>
+							<FormField
+								control={sourceForm.control}
+								name="branch"
+								render={({ field }) => (
+									<FormItem>
+										<FormControl>
+											<Input
+												placeholder="master"
+												className="lowercase"
+												{...field}
+												disabled={disabled}
+											/>
+										</FormControl>
+										<FormMessage />
+									</FormItem>
+								)}
+							/>
+							<Label>Root Directory</Label>
+							<FormField
+								control={sourceForm.control}
+								name="rootDir"
+								render={({ field }) => (
+									<FormItem>
+										<FormControl>
+											<Input placeholder="/" {...field} disabled={disabled} />
+										</FormControl>
+										<FormMessage />
+									</FormItem>
+								)}
+							/>
+						</form>
+					</Form>
+				</AccordionContent>
+			</AccordionItem>
+		</Accordion>
+	);
+}
