From 1f7af4a957e5965006467ec83720f357706f03f1 Mon Sep 17 00:00:00 2001 From: Thom Shutt Date: Fri, 20 Feb 2026 12:28:42 +0000 Subject: [PATCH 1/9] Break out Plugins to top-level navbar element Signed-off-by: Thom Shutt --- frontend/src/components/Header.tsx | 45 +++- frontend/src/components/PluginsDialog.tsx | 294 +++++++++++++++++++++ frontend/src/components/SettingsDialog.tsx | 289 +------------------- frontend/src/types/index.ts | 14 + 4 files changed, 345 insertions(+), 297 deletions(-) create mode 100644 frontend/src/components/PluginsDialog.tsx diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 27941f9eb..580e77b99 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -1,7 +1,8 @@ import { useState, useEffect, useRef } from "react"; -import { Settings, Cloud, CloudOff } from "lucide-react"; +import { Settings, Cloud, CloudOff, Plug } from "lucide-react"; import { Button } from "./ui/button"; import { SettingsDialog } from "./SettingsDialog"; +import { PluginsDialog } from "./PluginsDialog"; import { toast } from "sonner"; import { useCloudStatus } from "../hooks/useCloudStatus"; @@ -22,8 +23,9 @@ export function Header({ onSettingsTabOpened, }: HeaderProps) { const [settingsOpen, setSettingsOpen] = useState(false); + const [pluginsOpen, setPluginsOpen] = useState(false); const [initialTab, setInitialTab] = useState< - "general" | "account" | "api-keys" | "plugins" + "general" | "account" | "api-keys" >("general"); const [initialPluginPath, setInitialPluginPath] = useState(""); @@ -79,13 +81,15 @@ export function Header({ setSettingsOpen(true); }; - // React to external requests to open a specific settings tab + // React to external requests to open a specific settings/plugins tab useEffect(() => { if (openSettingsTab) { - setInitialTab( - openSettingsTab as "general" | "account" | "api-keys" | "plugins" - ); - setSettingsOpen(true); + if (openSettingsTab === "plugins") { + setPluginsOpen(true); + } else { + setInitialTab(openSettingsTab as "general" | "account" | "api-keys"); + setSettingsOpen(true); + } onSettingsTabOpened?.(); } }, [openSettingsTab, onSettingsTabOpened]); @@ -95,17 +99,20 @@ export function Header({ if (window.scope?.onDeepLinkAction) { return window.scope.onDeepLinkAction(data => { if (data.action === "install-plugin" && data.package) { - setInitialTab("plugins"); setInitialPluginPath(data.package); - setSettingsOpen(true); + setPluginsOpen(true); } }); } }, []); - const handleClose = () => { + const handleSettingsClose = () => { setSettingsOpen(false); setInitialTab("general"); + }; + + const handlePluginsClose = () => { + setPluginsOpen(false); setInitialPluginPath(""); }; @@ -141,6 +148,15 @@ export function Header({ )} + + + ) : plugins.length === 0 ? ( +

+ {debouncedSearch + ? "No plugins found matching your search." + : "No plugins available."} +

+ ) : ( +
+ {plugins.map(plugin => { + const isInstalled = installedSet.has(plugin.name.toLowerCase()); + const daydreamUrl = + plugin.learnMoreUrl || + `${DAYDREAM_APP}/plugins?search=${encodeURIComponent(plugin.name)}`; + return ( +
+ {plugin.iconUrl ? ( + + ) : ( +
+ + {plugin.name.charAt(0).toUpperCase()} + +
+ )} +
+
+ + {plugin.name} + + {plugin.version && ( + + v{plugin.version} + + )} + + {plugin.pluginType} + +
+ {plugin.description && ( +

+ {plugin.description} +

+ )} + {plugin.tags.length > 0 && ( +
+ {plugin.tags.slice(0, 4).map(tag => ( + + {tag} + + ))} +
+ )} +
+
+ + {plugin.repositoryUrl && ( + + )} + {isInstalled ? ( + + Installed + + ) : ( + + )} +
+
+ ); + })} +
+ )} + + ); +} From febf3722e1594d532f50e80b6ab7a3a9f020d748 Mon Sep 17 00:00:00 2001 From: Thom Shutt Date: Fri, 20 Feb 2026 13:50:16 +0000 Subject: [PATCH 3/9] Remove unused dialog headers Signed-off-by: Thom Shutt --- frontend/src/components/PluginsDialog.tsx | 5 +---- frontend/src/components/SettingsDialog.tsx | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/PluginsDialog.tsx b/frontend/src/components/PluginsDialog.tsx index 35db2f9a0..bf4e02a1f 100644 --- a/frontend/src/components/PluginsDialog.tsx +++ b/frontend/src/components/PluginsDialog.tsx @@ -1,5 +1,5 @@ import { useState, useEffect, useCallback, useRef } from "react"; -import { Dialog, DialogContent, DialogHeader, DialogTitle } from "./ui/dialog"; +import { Dialog, DialogContent } from "./ui/dialog"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs"; import { PluginsTab } from "./settings/PluginsTab"; import { DiscoverTab } from "./settings/DiscoverTab"; @@ -253,9 +253,6 @@ export function PluginsDialog({ return ( !isOpen && onClose()}> - - Plugins - !isOpen && onClose()}> - - Settings - Date: Fri, 20 Feb 2026 13:53:58 +0000 Subject: [PATCH 4/9] Make URL bases overrideable, consistent with other parts of the app Signed-off-by: Thom Shutt --- frontend/src/components/settings/DiscoverTab.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/settings/DiscoverTab.tsx b/frontend/src/components/settings/DiscoverTab.tsx index 5d063e577..20a2680eb 100644 --- a/frontend/src/components/settings/DiscoverTab.tsx +++ b/frontend/src/components/settings/DiscoverTab.tsx @@ -4,8 +4,12 @@ import { Button } from "../ui/button"; import { Input } from "../ui/input"; import { Badge } from "../ui/badge"; -const DAYDREAM_API = "https://api.daydream.live"; -const DAYDREAM_APP = "https://app.daydream.live"; +const DAYDREAM_API_BASE = + (import.meta.env.VITE_DAYDREAM_API_BASE as string | undefined) || + "https://api.daydream.live"; +const DAYDREAM_APP_BASE = + (import.meta.env.VITE_DAYDREAM_APP_BASE as string | undefined) || + "https://app.daydream.live"; function GitHubIcon({ className }: { className?: string }) { return ( @@ -76,7 +80,7 @@ export function DiscoverTab({ params.set("search", debouncedSearch); } const response = await fetch( - `${DAYDREAM_API}/v1/plugins?${params.toString()}` + `${DAYDREAM_API_BASE}/v1/plugins?${params.toString()}` ); if (!response.ok) { throw new Error(`Failed to fetch plugins (${response.status})`); @@ -137,7 +141,7 @@ export function DiscoverTab({ const isInstalled = installedSet.has(plugin.name.toLowerCase()); const daydreamUrl = plugin.learnMoreUrl || - `${DAYDREAM_APP}/plugins?search=${encodeURIComponent(plugin.name)}`; + `${DAYDREAM_APP_BASE}/plugins?search=${encodeURIComponent(plugin.name)}`; return (
Date: Fri, 20 Feb 2026 14:37:55 +0000 Subject: [PATCH 5/9] Adapt plugins dialog based on window size Signed-off-by: Thom Shutt --- frontend/src/components/PluginsDialog.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/PluginsDialog.tsx b/frontend/src/components/PluginsDialog.tsx index bf4e02a1f..327181154 100644 --- a/frontend/src/components/PluginsDialog.tsx +++ b/frontend/src/components/PluginsDialog.tsx @@ -252,7 +252,7 @@ export function PluginsDialog({ return ( !isOpen && onClose()}> - +
-
+
Date: Fri, 20 Feb 2026 14:39:32 +0000 Subject: [PATCH 6/9] Don't show a Download button if the Github URL is missing for some reason Signed-off-by: Thom Shutt --- frontend/src/components/settings/DiscoverTab.tsx | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/settings/DiscoverTab.tsx b/frontend/src/components/settings/DiscoverTab.tsx index 20a2680eb..4d2a9ac22 100644 --- a/frontend/src/components/settings/DiscoverTab.tsx +++ b/frontend/src/components/settings/DiscoverTab.tsx @@ -232,23 +232,18 @@ export function DiscoverTab({ Installed - ) : ( + ) : plugin.repositoryUrl ? ( - )} + ) : null}
); From 10bed488e10c0959d4bea02e42cddc40f5c5d2c0 Mon Sep 17 00:00:00 2001 From: Thom Shutt Date: Fri, 20 Feb 2026 14:46:18 +0000 Subject: [PATCH 7/9] Don't show pluginType Signed-off-by: Thom Shutt --- frontend/src/components/settings/DiscoverTab.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/frontend/src/components/settings/DiscoverTab.tsx b/frontend/src/components/settings/DiscoverTab.tsx index 4d2a9ac22..72e6ec472 100644 --- a/frontend/src/components/settings/DiscoverTab.tsx +++ b/frontend/src/components/settings/DiscoverTab.tsx @@ -170,12 +170,6 @@ export function DiscoverTab({ v{plugin.version} )} - - {plugin.pluginType} -
{plugin.description && (

From 1e5c0e3eeb28b33c4df58fe34e6bd790a55bca64 Mon Sep 17 00:00:00 2001 From: Thom Shutt Date: Fri, 20 Feb 2026 14:52:31 +0000 Subject: [PATCH 8/9] Use more of the screen's height for overlay Signed-off-by: Thom Shutt --- frontend/src/components/PluginsDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/PluginsDialog.tsx b/frontend/src/components/PluginsDialog.tsx index 327181154..4c2519c52 100644 --- a/frontend/src/components/PluginsDialog.tsx +++ b/frontend/src/components/PluginsDialog.tsx @@ -274,7 +274,7 @@ export function PluginsDialog({

-
+
Date: Fri, 20 Feb 2026 15:17:59 +0000 Subject: [PATCH 9/9] Persist 'installed' state across restarts Signed-off-by: Thom Shutt --- frontend/src/components/PluginsDialog.tsx | 4 ++- .../src/components/settings/DiscoverTab.tsx | 25 ++++++++++++++----- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/PluginsDialog.tsx b/frontend/src/components/PluginsDialog.tsx index 4c2519c52..b6d4192bc 100644 --- a/frontend/src/components/PluginsDialog.tsx +++ b/frontend/src/components/PluginsDialog.tsx @@ -293,7 +293,9 @@ export function PluginsDialog({ p.name)} + installedRepoUrls={plugins + .map(p => p.package_spec) + .filter((s): s is string => !!s)} isInstalling={isInstalling} /> diff --git a/frontend/src/components/settings/DiscoverTab.tsx b/frontend/src/components/settings/DiscoverTab.tsx index 72e6ec472..5e9abe856 100644 --- a/frontend/src/components/settings/DiscoverTab.tsx +++ b/frontend/src/components/settings/DiscoverTab.tsx @@ -27,6 +27,7 @@ function GitHubIcon({ className }: { className?: string }) { interface DaydreamPlugin { id: string; creatorId: string; + creatorUsername: string; name: string; slug: string; description: string | null; @@ -46,15 +47,23 @@ interface DiscoverResponse { hasMore: boolean; } +function normalizeRepoUrl(url: string): string { + return url + .replace(/^git\+/, "") + .replace(/\.git$/, "") + .replace(/\/$/, "") + .toLowerCase(); +} + interface DiscoverTabProps { onInstall: (packageSpec: string) => void; - installedPluginNames: string[]; + installedRepoUrls: string[]; isInstalling?: boolean; } export function DiscoverTab({ onInstall, - installedPluginNames, + installedRepoUrls, isInstalling = false, }: DiscoverTabProps) { const [plugins, setPlugins] = useState([]); @@ -99,7 +108,7 @@ export function DiscoverTab({ fetchDiscoverPlugins(); }, [fetchDiscoverPlugins]); - const installedSet = new Set(installedPluginNames.map(n => n.toLowerCase())); + const installedSet = new Set(installedRepoUrls.map(normalizeRepoUrl)); return (
@@ -138,10 +147,14 @@ export function DiscoverTab({ ) : (
{plugins.map(plugin => { - const isInstalled = installedSet.has(plugin.name.toLowerCase()); + const isInstalled = plugin.repositoryUrl + ? installedSet.has(normalizeRepoUrl(plugin.repositoryUrl)) + : false; const daydreamUrl = - plugin.learnMoreUrl || - `${DAYDREAM_APP_BASE}/plugins?search=${encodeURIComponent(plugin.name)}`; + plugin.creatorUsername && plugin.slug + ? `${DAYDREAM_APP_BASE}/plugins/${plugin.creatorUsername}/${plugin.slug}` + : plugin.learnMoreUrl || + `${DAYDREAM_APP_BASE}/plugins?search=${encodeURIComponent(plugin.name)}`; return (