diff --git a/build.gradle b/build.gradle
index 6a30e1dbc3db4426ad26b3d9368f9c0ca527c774..e56089a891f96366e791474d5b0326423c450b2a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -9,7 +9,7 @@ plugins {
 group 'de.tudresden.inf.st'
 
 ext {
-    mainClassName = 'de.tudresden.inf.st.mg.Main'
+    mainClassName = 'de.tudresden.inf.st.mg.ImmersiveSortingController'
 }
 
 // set the main class name for `gradle run`
diff --git a/src/main/java/de/tudresden/inf/st/mg/Main.java b/src/main/java/de/tudresden/inf/st/mg/ImmersiveSortingController.java
similarity index 52%
rename from src/main/java/de/tudresden/inf/st/mg/Main.java
rename to src/main/java/de/tudresden/inf/st/mg/ImmersiveSortingController.java
index de555c72af11836d788b5276ff4d18759792af6f..d5efac843d5e4ff0f9d33d444218a8f83b95079d 100644
--- a/src/main/java/de/tudresden/inf/st/mg/Main.java
+++ b/src/main/java/de/tudresden/inf/st/mg/ImmersiveSortingController.java
@@ -3,9 +3,6 @@ package de.tudresden.inf.st.mg;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.module.SimpleModule;
 import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
-import com.google.protobuf.util.JsonFormat;
-import de.tudresden.inf.st.ceti.Scene;
-import de.tudresden.inf.st.mg.common.DiagramProvider;
 import de.tudresden.inf.st.mg.common.MotionGrammarConfig;
 import de.tudresden.inf.st.mg.common.MotionGrammarParser;
 import de.tudresden.inf.st.mg.common.TableDeserializer;
@@ -13,6 +10,9 @@ import de.tudresden.inf.st.mg.jastadd.model.JastAddList;
 import de.tudresden.inf.st.mg.jastadd.model.RobotWorld;
 import de.tudresden.inf.st.mg.jastadd.model.Table;
 import de.tudresden.inf.st.mg.jastadd.model.World;
+import io.javalin.Javalin;
+import io.javalin.core.JavalinConfig;
+import io.javalin.websocket.WsContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -21,19 +21,27 @@ import java.io.IOException;
 import java.net.URISyntaxException;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.time.Instant;
 import java.util.Comparator;
+import java.util.Date;
+import java.util.Map;
 import java.util.Random;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ConcurrentHashMap;
 
-public class Main {
-
-  private static final Logger logger = LoggerFactory.getLogger(Main.class);
+public class ImmersiveSortingController {
 
   public static final Path TIDY_AST_DIAGRAM_DIR = Path.of("src", "gen", "resources", "diagrams", "parsing", "tidy");
-
+  private static final Logger logger = LoggerFactory.getLogger(ImmersiveSortingController.class);
+  private static final Map<WsContext, String> clients = new ConcurrentHashMap<>();
+  private static ImmersiveSortingController INSTANCE;
   private final RobotWorld world;
+  private ParseThread parse;
+  private Diagram ast;
+  private Diagram context;
 
-  public Main(boolean simulate) {
+  private boolean initialized = false;
+
+  public ImmersiveSortingController(boolean simulate) {
     MotionGrammarConfig.astDiagramDir = TIDY_AST_DIAGRAM_DIR;
     try {
       Files.walk(TIDY_AST_DIAGRAM_DIR).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
@@ -51,11 +59,15 @@ public class Main {
       module.addDeserializer(Table.class, new TableDeserializer());
       ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
       mapper.registerModule(module);
-      world.setDemonstrationTable(mapper.readValue(Main.class.getResourceAsStream("config_scene_virtual-table.yaml"), Table.class));
+      world.setDemonstrationTable(mapper.readValue(ImmersiveSortingController.class.getResourceAsStream("config_scene_virtual-table.yaml"), Table.class));
     } catch (IOException e) {
       e.printStackTrace();
     }
 
+    initializeWebserver();
+
+    initialized = true;
+
     World.enableContextPrinting(true);
     world.printContext("initial");
 
@@ -90,7 +102,117 @@ public class Main {
 
   }
 
-  private ParseThread parse;
+  /**
+   * Runs the parser on the actual robot. For this to work, a robot controller must be connected to an MQTT server running
+   * on localhost:1883
+   */
+  public static void main(String[] args) {
+    ImmersiveSortingController controller = ImmersiveSortingController.getInstance();
+    try {
+      Thread.sleep(Long.MAX_VALUE);
+    } catch (InterruptedException ignored) {
+    }
+  }
+
+  public static ImmersiveSortingController getInstance() {
+    if (INSTANCE == null) {
+      INSTANCE = new ImmersiveSortingController(false);
+    }
+    return INSTANCE;
+  }
+
+  public void startParse() {
+    if (parse != null && parse.isAlive()) {
+      parse.stopParsing();
+      try {
+        parse.join();
+      } catch (InterruptedException ignored) {
+      }
+    }
+    parse = new ParseThread();
+    parse.start();
+  }
+
+  public void resetSelections() {
+    world.setSelectionList(new JastAddList<>());
+  }
+
+  public void printContext(String message) {
+    world.printContext(message);
+  }
+
+  private void initializeWebserver() {
+    ast = new Diagram(new Date(), 0, "<no rule>", "", "ast");
+    context = new Diagram(Date.from(Instant.now()), 0, "<no rule>", "", "context");
+
+    Javalin webserver = Javalin.create(JavalinConfig::enableCorsForAllOrigins).start(7070);
+    webserver.get("/", ctx -> ctx.result("the context is delivered at /context and the ast is delivered at /ast"));
+
+    webserver.get("/ast/", ctx -> {
+      ctx.json(ast);
+    });
+
+    webserver.get("/context/", ctx -> {
+      ctx.json(context);
+    });
+
+    webserver.ws("ast-events", ws -> {
+      ws.onConnect(ctx -> {
+        System.err.println("somebody connected " + ctx.host());
+        clients.put(ctx, ctx.host());
+      });
+      ws.onClose(ctx -> {
+        System.err.println("somebody disconnected");
+        clients.remove(ctx);
+      });
+      ws.onMessage(ctx -> {
+        if (initialized) {
+          logger.info("got message " + ctx.message());
+        } else {
+          logger.error("ignoring message " + ctx.message() + ", because system is not initialized yet");
+          return;
+        }
+        if ("parse".equals(ctx.message())) {
+          startParse();
+        } else if ("reset".equals(ctx.message())) {
+          resetSelections();
+          printContext("reset selection");
+        }
+      });
+    });
+  }
+
+  public void publish(Date timestamp, int step, String parseRule, Path diagramPath, String type) {
+    try {
+      if ("ast".equals(type)) {
+        ast = new Diagram(timestamp, step, parseRule, Files.readString(diagramPath), type);
+        clients.keySet().forEach(session -> session.send(ast));
+      } else if ("context".equals(type)) {
+        context = new Diagram(timestamp, step, parseRule, Files.readString(diagramPath), type);
+        clients.keySet().forEach(session -> session.send(context));
+      }
+    } catch (IOException e) {
+      System.err.println("Unable to read " + type + " diagram file " + diagramPath);
+      e.printStackTrace();
+    }
+  }
+
+  public static class Diagram {
+    public Date timestamp;
+    public int step;
+    public String parseRule;
+    public String diagram;
+
+    public String type;
+
+    public Diagram(Date timestamp, int step, String parseRule, String diagram, String type) {
+      this.timestamp = timestamp;
+      this.step = step;
+      this.parseRule = parseRule;
+      this.diagram = diagram;
+      this.type = type;
+    }
+  }
 
   class ParseThread extends Thread {
 
@@ -104,7 +226,7 @@ public class Main {
 
       // parse (synchronously, long-running)
       try {
-        var result = parser.parse();
+        parser.parse();
         logger.info("parsing completed!");
       } catch (MotionGrammarParser.ParseException e) {
         throw new RuntimeException(e);
@@ -118,36 +240,4 @@ public class Main {
     }
   }
 
-  public void startParse() {
-    if (parse != null && parse.isAlive()) {
-      parse.stopParsing();
-      try {
-        parse.join();
-      } catch (InterruptedException e) {
-      }
-    }
-    parse = new ParseThread();
-    parse.start();
-  }
-
-  public void resetSelections() {
-    world.setSelectionList(new JastAddList<>());
-  }
-
-  /**
-   * Runs the parser on the actual robot. For this to work, a robot controller must be connected to an MQTT server running
-   * on localhost:1883
-   */
-  public static void main(String[] args) {
-    DiagramProvider webController = DiagramProvider.getInstance();
-    webController.setController(new Main(false));
-    try {
-      Thread.sleep(Long.MAX_VALUE);
-    } catch (InterruptedException ignored) {
-    }
-  }
-
-  public void printContext(String message) {
-    world.printContext(message);
-  }
 }
diff --git a/src/main/java/de/tudresden/inf/st/mg/common/DiagramProvider.java b/src/main/java/de/tudresden/inf/st/mg/common/DiagramProvider.java
deleted file mode 100644
index 1a3d247f4b37c3146aad3375f5d60e82a3e37eca..0000000000000000000000000000000000000000
--- a/src/main/java/de/tudresden/inf/st/mg/common/DiagramProvider.java
+++ /dev/null
@@ -1,108 +0,0 @@
-package de.tudresden.inf.st.mg.common;
-
-import de.tudresden.inf.st.mg.Main;
-import io.javalin.Javalin;
-import io.javalin.core.JavalinConfig;
-import io.javalin.websocket.WsContext;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-public class DiagramProvider {
-
-  private static DiagramProvider INSTANCE;
-
-  private Main controller;
-
-  private static final Map<WsContext, String> clients = new ConcurrentHashMap<>();
-
-  public static DiagramProvider getInstance() {
-    if (INSTANCE == null) {
-      INSTANCE = new DiagramProvider();
-    }
-    return INSTANCE;
-  }
-
-  private AST ast;
-  private AST context;
-
-  private DiagramProvider() {
-    ast = new AST(new Date(), 0, "<no rule>", "", "ast");
-    context = new AST(Date.from(Instant.now()), 0, "<no rule>", "", "context");
-
-    Javalin app = Javalin.create(JavalinConfig::enableCorsForAllOrigins).start(7070);
-    app.get("/", ctx -> ctx.result("the context is delivered at /context and the ast is delivered at /ast"));
-
-    app.get("/ast/", ctx -> {
-      ctx.json(ast);
-    });
-
-    app.get("/context/", ctx -> {
-      ctx.json(context);
-    });
-
-    app.ws("ast-events", ws -> {
-      ws.onConnect(ctx -> {
-        System.err.println("somebody connected " + ctx.host());
-        clients.put(ctx, ctx.host());
-      });
-      ws.onClose(ctx -> {
-        System.err.println("somebody disconnected");
-        clients.remove(ctx);
-      });
-      ws.onMessage(ctx -> {
-        System.out.println("got message " + ctx.message());
-        if ("parse".equals(ctx.message()) && controller != null) {
-          controller.startParse();
-        } else if ("reset".equals(ctx.message()) && controller != null) {
-          controller.resetSelections();
-          controller.printContext("reset selection");
-        }
-      });
-    });
-  }
-
-  public void publish(Date timestamp, int step, String parseRule, Path diagramPath, String type) {
-    try {
-      if ("ast".equals(type)) {
-        ast = new AST(timestamp, step, parseRule, Files.readString(diagramPath), type);
-        clients.keySet().forEach(session -> session.send(ast));
-      } else if ("context".equals(type)) {
-        context = new AST(timestamp, step, parseRule, Files.readString(diagramPath), type);
-        clients.keySet().forEach(session -> session.send(context));
-      }
-    } catch (IOException e) {
-      System.err.println("Unable to read " + type + " diagram file " + diagramPath);
-      e.printStackTrace();
-    }
-  }
-
-  public void setController(Main controller) {
-    this.controller = controller;
-  }
-
-  public static class AST {
-    public Date timestamp;
-    public int step;
-    public String parseRule;
-    public String diagram;
-
-    public String type;
-
-    public AST(Date timestamp, int step, String parseRule, String diagram, String type) {
-      this.timestamp = timestamp;
-      this.step = step;
-      this.parseRule = parseRule;
-      this.diagram = diagram;
-      this.type = type;
-    }
-  }
-
-}