diff --git a/applications/gui2/CMakeLists.txt b/applications/gui2/CMakeLists.txt
index 7dce75891115c42d423e3d674dc0626f37592ed5..a4c7beacea4843a25809c4cea7207ab7358eea76 100644
--- a/applications/gui2/CMakeLists.txt
+++ b/applications/gui2/CMakeLists.txt
@@ -25,6 +25,7 @@ set(GUI2SRC
 	src/widgets/popupbutton.cpp
 	src/widgets/imageview.cpp
 	src/widgets/combobox.cpp
+	src/widgets/leftbutton.cpp
 )
 
 add_gui_module("themes")
diff --git a/applications/gui2/src/modules/addsource.cpp b/applications/gui2/src/modules/addsource.cpp
index 2bbc90ef7a7b744b72c9a7fea70f0b264268d2a0..8b0aa3267f086282a0bec2337053cd58151abcf4 100644
--- a/applications/gui2/src/modules/addsource.cpp
+++ b/applications/gui2/src/modules/addsource.cpp
@@ -33,6 +33,10 @@ std::vector<std::string> AddCtrl::getHosts() {
 	return std::move(io->feed()->knownHosts());
 }
 
+std::vector<std::string> AddCtrl::getGroups() {
+	return {};
+}
+
 std::set<ftl::stream::SourceInfo> AddCtrl::getRecent() {
 	return std::move(io->feed()->recentSources());
 }
diff --git a/applications/gui2/src/modules/addsource.hpp b/applications/gui2/src/modules/addsource.hpp
index 7e6570bb4357793281ce102bec93ad48865f1d45..b76daad9b0575ac1d78432bbc1dcd5f9abbb6562 100644
--- a/applications/gui2/src/modules/addsource.hpp
+++ b/applications/gui2/src/modules/addsource.hpp
@@ -26,6 +26,7 @@ public:
 	std::vector<std::string> getNetSources();
 	std::vector<std::string> getFileSources();
 	std::vector<std::string> getDeviceSources();
+	std::vector<std::string> getGroups();
 	std::string getSourceName(const std::string &uri);
 	bool isSourceActive(const std::string &uri);
 
diff --git a/applications/gui2/src/modules/themes.cpp b/applications/gui2/src/modules/themes.cpp
index 87ed20bbc223b5b755547307aacc7fe601dcc1d8..67f7f7760defd4181449e8f6d88a99cb3245c92e 100644
--- a/applications/gui2/src/modules/themes.cpp
+++ b/applications/gui2/src/modules/themes.cpp
@@ -65,9 +65,9 @@ void Themes::init() {
 	windowtheme_dark->mButtonGradientTopPushed = nanogui::Color(50,255);
 	windowtheme_dark->mButtonGradientBotPushed = nanogui::Color(90,255);*/
 	windowtheme_dark->mButtonGradientBotFocused = nanogui::Color(60,255);
-	windowtheme_dark->mButtonGradientBotUnfocused = nanogui::Color(30,30,40,180);
+	windowtheme_dark->mButtonGradientBotUnfocused = nanogui::Color(35,35,40,180);
 	windowtheme_dark->mButtonGradientTopFocused = nanogui::Color(60,255);
-	windowtheme_dark->mButtonGradientTopUnfocused = nanogui::Color(30,30,40,180);
+	windowtheme_dark->mButtonGradientTopUnfocused = nanogui::Color(35,35,40,180);
 	windowtheme_dark->mButtonGradientTopPushed = nanogui::Color(90,180);
 	windowtheme_dark->mButtonGradientBotPushed = nanogui::Color(90,180);
 	windowtheme_dark->mButtonFontSize = 16;
diff --git a/applications/gui2/src/views/addsource.cpp b/applications/gui2/src/views/addsource.cpp
index 83fb59d9be9ce2b41521bd3a700fcc50a4c8b955..9ca1b0d1b1f6254b7f80131ef3be8f818aff3b0d 100644
--- a/applications/gui2/src/views/addsource.cpp
+++ b/applications/gui2/src/views/addsource.cpp
@@ -104,6 +104,20 @@ void AddSourceWindow::rebuild() {
 		_addButton(s.uri, recentscroll);
 	}
 
+	auto *group_tab = tabs->createTab("Groups");
+	group_tab->setLayout(new nanogui::BoxLayout
+				(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 0, 0));
+	vscroll = new VScrollPanel(group_tab);
+	vscroll->setFixedHeight(150);
+	Widget *groupscroll = new Widget(vscroll);
+	groupscroll->setLayout(new BoxLayout(Orientation::Vertical, Alignment::Fill, 10, 4));
+
+	auto groups = ctrl_->getGroups();
+
+	for (auto &s : groups) {
+		_addButton(s, groupscroll);
+	}
+
 	auto *dev_tab = tabs->createTab("Devices");
 	dev_tab->setLayout(new nanogui::BoxLayout
 				(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 0, 0));
@@ -126,6 +140,15 @@ void AddSourceWindow::rebuild() {
 	Widget *hostscroll = new Widget(vscroll);
 	hostscroll->setLayout(new BoxLayout(Orientation::Vertical, Alignment::Fill, 10, 4));
 
+	button = new Button(hostscroll, "Add Host", ENTYPO_ICON_PLUS);
+	button->setIconPosition(Button::IconPosition::Left);
+	button->setIconExtraScale(1.2);
+	button->setFontSize(18);
+	button->setTooltip("Manually add a new connection URI");
+	button->setCallback([this]() {
+
+	});
+
 	auto hostsrcs = ctrl_->getHosts();
 
 	for (auto &s : hostsrcs) {
@@ -154,7 +177,7 @@ void AddSourceWindow::rebuild() {
 	Widget *filescroll = new Widget(vscroll);
 	filescroll->setLayout(new BoxLayout(Orientation::Vertical, Alignment::Fill, 10, 4));
 
-	button = new Button(filescroll, "Open", ENTYPO_ICON_FOLDER);
+	button = new Button(filescroll, "Open", ENTYPO_ICON_PLUS);
 	button->setIconPosition(Button::IconPosition::Left);
 	button->setIconExtraScale(1.2);
 	button->setFontSize(18);
diff --git a/applications/gui2/src/views/config.cpp b/applications/gui2/src/views/config.cpp
index 17a0ec07ce8fef46ef3ef0f53a51939263a565b0..896f773ccb4ea309ae67d4322aa3253d79eee7c4 100644
--- a/applications/gui2/src/views/config.cpp
+++ b/applications/gui2/src/views/config.cpp
@@ -16,6 +16,7 @@
 
 #include "config.hpp"
 #include "../screen.hpp"
+#include "../widgets/leftbutton.hpp"
 
 using ftl::gui2::ConfigWindow;
 using std::string;
@@ -85,7 +86,7 @@ ConfigWindow::ConfigWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl)
 
 	using namespace nanogui;
 
-	setTheme(dynamic_cast<ftl::gui2::Screen*>(screen())->getTheme("window_light"));
+	setTheme(dynamic_cast<ftl::gui2::Screen*>(screen())->getTheme("window_dark"));
 
 	auto close = new nanogui::Button(buttonPanel(), "", ENTYPO_ICON_CROSS);
 	close->setTheme(dynamic_cast<ftl::gui2::Screen*>(screen())->getTheme("window_dark"));
@@ -93,6 +94,7 @@ ConfigWindow::ConfigWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl)
 	close->setCallback([this](){ dispose();});
 
 	setLayout(new GroupLayout(15, 6, 14, 10));
+	setFixedWidth(400);
 	setPosition(Vector2i(parent->width()/2.0f - 100.0f, parent->height()/2.0f - 100.0f));
 
 	auto configurables = ftl::config::list();
@@ -110,7 +112,37 @@ ConfigWindow::ConfigWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl)
 	searchBox->setButtons(buttons);
 
 	std::vector<std::string> configurable_titles(size);
-	for (size_t i = 0; i < size; ++i) {
+
+	std::set<std::string> sorted_cfgs;
+	sorted_cfgs.insert(configurables.begin(), configurables.end());
+
+	for (auto &c : sorted_cfgs) {
+		ftl::URI uri(c);
+
+		std::string spacing = "";
+		for (size_t i=0; i<uri.getPathLength(); ++i) {
+			spacing += "    ";
+		}
+
+		//if (uri.getFragment().size() == 0) {
+			std::string title = spacing + titleForURI(uri);
+			//configurable_titles[i] = title;
+			auto itembutton = new ftl::gui2::LeftButton(buttons, title);
+
+			/*if (_isEmpty(c)) {
+				itembutton->setEnabled(false);
+			}*/
+			itembutton->setTooltip(c);
+			//itembutton->setBackgroundColor(nanogui::Color(0.9f,0.9f,0.9f,0.9f));
+			itembutton->setCallback([this,c]() {
+				_buildForm(c);
+				setVisible(false);
+				dispose();
+			});
+		//}
+	}
+
+	/*for (size_t i = 0; i < size; ++i) {
 		ftl::URI uri(configurables[i]);
 		std::string label = uri.getFragment();
 
@@ -149,7 +181,7 @@ ConfigWindow::ConfigWindow(nanogui::Widget *parent, ftl::ctrl::Master *ctrl)
 			setVisible(false);
 			dispose();
 		});
-	}
+	}*/
 }
 
 ConfigWindow::~ConfigWindow() {
@@ -213,10 +245,11 @@ void ConfigWindow::__addElements(nanogui::FormHelper *form, const std::string &s
 			string nuri;
 
 			// Checking the URI with exists() prevents unloaded local configurations from being shown.
-			if (suri.find('#') != string::npos && exists(suri+string("/")+key)) {
+			//if (suri.find('#') != string::npos && exists(suri+string("/")+key)) {
+			//	nuri = suri+string("/")+key;
+			//} else
+			if (exists(suri+string("/")+key)) {
 				nuri = suri+string("/")+key;
-			} else if (exists(suri+string("#")+key)) {
-				nuri = suri+string("#")+key;
 			}
 
 			if (!nuri.empty()) {
@@ -226,6 +259,7 @@ void ConfigWindow::__addElements(nanogui::FormHelper *form, const std::string &s
 				});
 
 				button->setIcon(ENTYPO_ICON_FOLDER);
+				button->setIconPosition(nanogui::Button::IconPosition::Left);
 				if (_isEmpty(nuri)) {
 					button->setEnabled(false);
 				}
@@ -284,7 +318,7 @@ void ConfigWindow::buildForm(nanogui::Screen *screen, const std::string &suri) {
 	ftl::URI uri(suri);
 
 	FormHelper *form = new FormHelper(screen);
-	form->addWindow(Vector2i(100,50), uri.getFragment());
+	form->addWindow(Vector2i(100,50), titleForURI(uri));
 	//form->window()->setTheme(theme());
 
 	{
@@ -292,6 +326,10 @@ void ConfigWindow::buildForm(nanogui::Screen *screen, const std::string &suri) {
 		existing_configs[suri] = form->window();
 	}
 
+	auto *window = form->window();
+	window->setTheme(dynamic_cast<ftl::gui2::Screen*>(window->screen())->getTheme("window_dark"));
+	window->setWidth(200);
+
 	__addElements(form, suri);
 
 	// prevent parent window from being destroyed too early
@@ -301,8 +339,6 @@ void ConfigWindow::buildForm(nanogui::Screen *screen, const std::string &suri) {
 	close->setTheme(dynamic_cast<ftl::gui2::Screen*>(screen)->getTheme("window_dark"));
 	//close->setBackgroundColor(theme()->mWindowHeaderGradientBot);
 
-	auto *window = form->window();
-
 	close->setCallback([window, suri](){
 		window->dispose();
 		//decRef();
diff --git a/applications/gui2/src/widgets/leftbutton.cpp b/applications/gui2/src/widgets/leftbutton.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c448ebaf2474674d8acfcaf2a7f783869154dc5f
--- /dev/null
+++ b/applications/gui2/src/widgets/leftbutton.cpp
@@ -0,0 +1,121 @@
+#include "leftbutton.hpp"
+#include <nanogui/button.h>
+#include <nanogui/theme.h>
+#include <nanogui/opengl.h>
+
+void ftl::gui2::LeftButton::draw(NVGcontext* ctx) {
+	using namespace nanogui;
+
+	Widget::draw(ctx);
+
+    NVGcolor gradTop = mTheme->mButtonGradientTopUnfocused;
+    NVGcolor gradBot = mTheme->mButtonGradientBotUnfocused;
+
+    if (mPushed) {
+        gradTop = mTheme->mButtonGradientTopPushed;
+        gradBot = mTheme->mButtonGradientBotPushed;
+    } else if (mMouseFocus && mEnabled) {
+        gradTop = mTheme->mButtonGradientTopFocused;
+        gradBot = mTheme->mButtonGradientBotFocused;
+    }
+
+    nvgBeginPath(ctx);
+
+    nvgRoundedRect(ctx, mPos.x() + 1, mPos.y() + 1.0f, mSize.x() - 2,
+                   mSize.y() - 2, mTheme->mButtonCornerRadius - 1);
+
+    if (mBackgroundColor.w() != 0) {
+        nvgFillColor(ctx, Color(mBackgroundColor.head<3>(), 1.f));
+        nvgFill(ctx);
+        if (mPushed) {
+            gradTop.a = gradBot.a = 0.8f;
+        } else {
+            double v = 1 - mBackgroundColor.w();
+            gradTop.a = gradBot.a = mEnabled ? v : v * .5f + .5f;
+        }
+    }
+
+    NVGpaint bg = nvgLinearGradient(ctx, mPos.x(), mPos.y(), mPos.x(),
+                                    mPos.y() + mSize.y(), gradTop, gradBot);
+
+    nvgFillPaint(ctx, bg);
+    nvgFill(ctx);
+
+    nvgBeginPath(ctx);
+    nvgStrokeWidth(ctx, 1.0f);
+    nvgRoundedRect(ctx, mPos.x() + 0.5f, mPos.y() + (mPushed ? 0.5f : 1.5f), mSize.x() - 1,
+                   mSize.y() - 1 - (mPushed ? 0.0f : 1.0f), mTheme->mButtonCornerRadius);
+    nvgStrokeColor(ctx, mTheme->mBorderLight);
+    nvgStroke(ctx);
+
+    nvgBeginPath(ctx);
+    nvgRoundedRect(ctx, mPos.x() + 0.5f, mPos.y() + 0.5f, mSize.x() - 1,
+                   mSize.y() - 2, mTheme->mButtonCornerRadius);
+    nvgStrokeColor(ctx, mTheme->mBorderDark);
+    nvgStroke(ctx);
+
+    int fontSize = mFontSize == -1 ? mTheme->mButtonFontSize : mFontSize;
+    nvgFontSize(ctx, fontSize);
+    nvgFontFace(ctx, "sans-bold");
+    float tw = nvgTextBounds(ctx, 0,0, mCaption.c_str(), nullptr, nullptr);
+
+    Vector2f center = mPos.cast<float>() + mSize.cast<float>() * 0.5f;
+    Vector2f textPos(mPos.x() + 8, center.y() - 1);
+    NVGcolor textColor =
+        mTextColor.w() == 0 ? mTheme->mTextColor : mTextColor;
+    if (!mEnabled)
+        textColor = mTheme->mDisabledTextColor;
+
+    if (mIcon) {
+        auto icon = utf8(mIcon);
+
+        float iw, ih = fontSize;
+        if (nvgIsFontIcon(mIcon)) {
+            ih *= icon_scale();
+            nvgFontSize(ctx, ih);
+            nvgFontFace(ctx, "icons");
+            iw = nvgTextBounds(ctx, 0, 0, icon.data(), nullptr, nullptr);
+        } else {
+            int w, h;
+            ih *= 0.9f;
+            nvgImageSize(ctx, mIcon, &w, &h);
+            iw = w * ih / h;
+        }
+        if (mCaption != "")
+            iw += mSize.y() * 0.15f;
+        nvgFillColor(ctx, textColor);
+        nvgTextAlign(ctx, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
+        Vector2f iconPos = center;
+        iconPos.y() -= 1;
+
+        if (mIconPosition == IconPosition::LeftCentered) {
+            iconPos.x() -= (tw + iw) * 0.5f;
+            textPos.x() += iw * 0.5f;
+        } else if (mIconPosition == IconPosition::RightCentered) {
+            textPos.x() -= iw * 0.5f;
+            iconPos.x() += tw * 0.5f;
+        } else if (mIconPosition == IconPosition::Left) {
+            iconPos.x() = mPos.x() + 8;
+        } else if (mIconPosition == IconPosition::Right) {
+            iconPos.x() = mPos.x() + mSize.x() - iw - 8;
+        }
+
+        if (nvgIsFontIcon(mIcon)) {
+            nvgText(ctx, iconPos.x(), iconPos.y()+1, icon.data(), nullptr);
+        } else {
+            NVGpaint imgPaint = nvgImagePattern(ctx,
+                    iconPos.x(), iconPos.y() - ih/2, iw, ih, 0, mIcon, mEnabled ? 0.5f : 0.25f);
+
+            nvgFillPaint(ctx, imgPaint);
+            nvgFill(ctx);
+        }
+    }
+
+    nvgFontSize(ctx, fontSize);
+    nvgFontFace(ctx, "sans-bold");
+    nvgTextAlign(ctx, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
+    nvgFillColor(ctx, mTheme->mTextColorShadow);
+    nvgText(ctx, textPos.x(), textPos.y(), mCaption.c_str(), nullptr);
+    nvgFillColor(ctx, textColor);
+    nvgText(ctx, textPos.x(), textPos.y() + 1, mCaption.c_str(), nullptr);
+}
\ No newline at end of file
diff --git a/applications/gui2/src/widgets/leftbutton.hpp b/applications/gui2/src/widgets/leftbutton.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..51efe156beb4e3b3ae4ce1b3f5c9d8cb58694cf4
--- /dev/null
+++ b/applications/gui2/src/widgets/leftbutton.hpp
@@ -0,0 +1,23 @@
+#pragma once
+#include <nanogui/button.h>
+
+namespace ftl {
+namespace gui2 {
+
+/**
+ * Allow left aligned button text.
+ */
+class LeftButton : public nanogui::Button {
+public:
+	LeftButton(nanogui::Widget *parent, const std::string &caption = "",
+				int buttonIcon = 0) : nanogui::Button(parent, caption, buttonIcon) {};
+	virtual ~LeftButton() {};
+
+	virtual void draw(NVGcontext* ctx) override;
+
+public:
+	EIGEN_MAKE_ALIGNED_OPERATOR_NEW
+};
+
+}
+}
diff --git a/components/common/cpp/include/ftl/uri.hpp b/components/common/cpp/include/ftl/uri.hpp
index 3a80439fca816ace6ecec81cfc80af921c3b848a..49bf3e6533070aad927a036c2a24223bd53e14d6 100644
--- a/components/common/cpp/include/ftl/uri.hpp
+++ b/components/common/cpp/include/ftl/uri.hpp
@@ -32,7 +32,8 @@ namespace ftl {
 			SCHEME_IPC,
 			SCHEME_FILE,
 			SCHEME_OTHER,
-			SCHEME_DEVICE
+			SCHEME_DEVICE,
+			SCHEME_GROUP
 		};
 
 		bool isValid() const { return m_valid; };
@@ -57,6 +58,8 @@ namespace ftl {
 
 		std::string getPathSegment(int n) const;
 
+		inline size_t getPathLength() const { return m_pathseg.size(); }
+
 		void setAttribute(const std::string &key, const std::string &value);
 		void setAttribute(const std::string &key, int value);
 
diff --git a/components/common/cpp/src/configuration.cpp b/components/common/cpp/src/configuration.cpp
index 29ee191f098ff7d13f72d1bf933202641ac40aab..96044339473e25f3f92093548b0eafcc6c7d65a1 100644
--- a/components/common/cpp/src/configuration.cpp
+++ b/components/common/cpp/src/configuration.cpp
@@ -631,7 +631,7 @@ static void process_options(Configurable *root, const map<string, string> &opts)
 			auto v = nlohmann::json::parse(opt.second);
 			ftl::config::update(*root->get<string>("$id") + string("/") + opt.first, v);
 		} catch(...) {
-			LOG(ERROR) << "Unrecognised option: " << *root->get<string>("$id") << "#" << opt.first;
+			LOG(ERROR) << "Unrecognised option: " << *root->get<string>("$id") << "/" << opt.first;
 		}
 	}
 }
@@ -734,22 +734,22 @@ std::vector<nlohmann::json*> ftl::config::_createArray(ftl::Configurable *parent
 			if (entity.is_object()) {
 				if (!entity["$id"].is_string()) {
 					std::string id_str = *parent->get<std::string>("$id");
-					if (id_str.find('#') != std::string::npos) {
+					//if (id_str.find('#') != std::string::npos) {
 						entity["$id"] = id_str + std::string("/") + name + std::string("/") + std::to_string(i);
-					} else {
-						entity["$id"] = id_str + std::string("#") + name + std::string("/") + std::to_string(i);
-					}
+					//} else {
+					//	entity["$id"] = id_str + std::string("#") + name + std::string("/") + std::to_string(i);
+					//}
 				}
 
 				result.push_back(&entity);
 			} else if (entity.is_null()) {
 				// Must create the object from scratch...
 				std::string id_str = *parent->get<std::string>("$id");
-				if (id_str.find('#') != std::string::npos) {
+				//if (id_str.find('#') != std::string::npos) {
 					id_str = id_str + std::string("/") + name + std::string("/") + std::to_string(i);
-				} else {
-					id_str = id_str + std::string("#") + name + std::string("/") + std::to_string(i);
-				}
+				//} else {
+				//	id_str = id_str + std::string("#") + name + std::string("/") + std::to_string(i);
+				//}
 				parent->getConfig()[name] = {
 					// cppcheck-suppress constStatement
 					{"$id", id_str}
@@ -775,22 +775,22 @@ nlohmann::json &ftl::config::_create(ftl::Configurable *parent, const std::strin
 	if (entity.is_object()) {
 		if (!entity["$id"].is_string()) {
 			std::string id_str = *parent->get<std::string>("$id");
-			if (id_str.find('#') != std::string::npos) {
+			//if (id_str.find('#') != std::string::npos) {
 				entity["$id"] = id_str + std::string("/") + name;
-			} else {
-				entity["$id"] = id_str + std::string("#") + name;
-			}
+			//} else {
+			//	entity["$id"] = id_str + std::string("#") + name;
+			//}
 		}
 
 		return entity;
 	} else if (entity.is_null()) {
 		// Must create the object from scratch...
 		std::string id_str = *parent->get<std::string>("$id");
-		if (id_str.find('#') != std::string::npos) {
+		//if (id_str.find('#') != std::string::npos) {
 			id_str = id_str + std::string("/") + name;
-		} else {
-			id_str = id_str + std::string("#") + name;
-		}
+		//} else {
+		//	id_str = id_str + std::string("#") + name;
+		//}
 		parent->getConfig()[name] = {
 			// cppcheck-suppress constStatement
 			{"$id", id_str}
diff --git a/components/common/cpp/src/uri.cpp b/components/common/cpp/src/uri.cpp
index 9311b841b2fd227d16f63c572b0ba5dcb80a454c..db5bb12d8ec895eb164d946d6c5c018d5187ab7c 100644
--- a/components/common/cpp/src/uri.cpp
+++ b/components/common/cpp/src/uri.cpp
@@ -82,6 +82,7 @@ void URI::_parse(uri_t puri) {
 		else if (prototext == "ipc") m_proto = SCHEME_IPC;
 		else if (prototext == "device") m_proto = SCHEME_DEVICE;
 		else if (prototext == "file") m_proto = SCHEME_FILE;
+		else if (prototext == "group") m_proto = SCHEME_GROUP;
 		else m_proto = SCHEME_OTHER;
 		m_protostr = prototext;
 
diff --git a/components/streams/src/feed.cpp b/components/streams/src/feed.cpp
index a06e43b04663f072e3bee641a162af0704360e85..a4bea0efeff283f61d59647417caa35bd9b33eb6 100644
--- a/components/streams/src/feed.cpp
+++ b/components/streams/src/feed.cpp
@@ -563,6 +563,7 @@ std::set<ftl::stream::SourceInfo> Feed::recentSources() {
 	for (auto &f : recent.items()) {
 		ftl::stream::SourceInfo info;
 		info.uri = f.key();
+		if (f.value().contains("uri")) info.uri = f.value()["uri"].get<std::string>();
 		info.last_used = f.value()["last_open"].get<int64_t>();
 		result.insert(info);
 	}
@@ -641,7 +642,7 @@ std::string Feed::getName(const std::string &puri) {
 		if (uri.getPathSegment(0) == "realsense") return "Realsense";
 		if (uri.getPathSegment(0) == "screen") return "Screen Capture";
 		if (uri.getPathSegment(0) == "render") return "3D Virtual";
-		if (uri.getPathSegment(0) == "openvr") return "VR";
+		if (uri.getPathSegment(0) == "openvr") return "OpenVR";
 		return "Unknown Device";
 	} else if (uri.getScheme() == ftl::URI::SCHEME_FILE) {
 		return getConfig()["recent_files"][uri.getBaseURI()].value("name", "FTLFile");
@@ -663,6 +664,7 @@ nlohmann::json &Feed::_add_recent_source(const ftl::URI &uri) {
 		name = name.substr(0, name.find_last_of('.'));
 	}
 
+	details["uri"] = uri.to_string();
 	details["name"] = name;
 	details["last_open"] = ftl::timer::get_time();
 	return details;