diff --git a/canvas/src/app/page.tsx b/canvas/src/app/page.tsx index b8976a35..74291409 100644 --- a/canvas/src/app/page.tsx +++ b/canvas/src/app/page.tsx @@ -1,9 +1,10 @@ "use client"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { Canvas } from "@/components/Canvas"; import { Legend } from "@/components/Legend"; import { CommunicationOverlay } from "@/components/CommunicationOverlay"; +import { Spinner } from "@/components/Spinner"; import { connectSocket, disconnectSocket } from "@/store/socket"; import { useCanvasStore } from "@/store/canvas"; import { api } from "@/lib/api"; @@ -12,6 +13,7 @@ import type { WorkspaceData } from "@/store/socket"; export default function Home() { const hydrationError = useCanvasStore((s) => s.hydrationError); const setHydrationError = useCanvasStore((s) => s.setHydrationError); + const [hydrating, setHydrating] = useState(true); useEffect(() => { connectSocket(); @@ -31,6 +33,8 @@ export default function Home() { useCanvasStore.getState().setHydrationError( err instanceof Error && err.message ? err.message : "Failed to load canvas" ); + }).finally(() => { + setHydrating(false); }); return () => { @@ -38,6 +42,17 @@ export default function Home() { }; }, []); + if (hydrating) { + return ( +
+
+ + Loading canvas... +
+
+ ); + } + return ( <> diff --git a/canvas/src/components/SidePanel.tsx b/canvas/src/components/SidePanel.tsx index d9bef424..c318b29e 100644 --- a/canvas/src/components/SidePanel.tsx +++ b/canvas/src/components/SidePanel.tsx @@ -173,7 +173,7 @@ export function SidePanel() { else if (e.key === "End") { e.preventDefault(); next = TABS.length - 1; } if (next !== null) { setPanelTab(TABS[next].id); - requestAnimationFrame(() => { document.getElementById(`tab-${TABS[next!].id}`)?.focus(); }); + requestAnimationFrame(() => { const el = document.getElementById(`tab-${TABS[next!].id}`); el?.focus(); el?.scrollIntoView({ block: "nearest", inline: "nearest" }); }); } }} > diff --git a/canvas/src/components/tabs/ChannelsTab.tsx b/canvas/src/components/tabs/ChannelsTab.tsx index 5249dba1..78cb628f 100644 --- a/canvas/src/components/tabs/ChannelsTab.tsx +++ b/canvas/src/components/tabs/ChannelsTab.tsx @@ -141,19 +141,29 @@ export function ChannelsTab({ workspaceId }: Props) { } }; + const [error, setError] = useState(""); + const handleToggle = async (ch: Channel) => { - await api.patch(`/workspaces/${workspaceId}/channels/${ch.id}`, { - enabled: !ch.enabled, - }); - load(); + try { + await api.patch(`/workspaces/${workspaceId}/channels/${ch.id}`, { + enabled: !ch.enabled, + }); + load(); + } catch (e: unknown) { + setError(e instanceof Error ? e.message : "Failed to toggle channel"); + } }; const confirmDelete = async () => { if (!pendingDelete) return; const ch = pendingDelete; setPendingDelete(null); - await api.del(`/workspaces/${workspaceId}/channels/${ch.id}`); - load(); + try { + await api.del(`/workspaces/${workspaceId}/channels/${ch.id}`); + load(); + } catch (e: unknown) { + setError(e instanceof Error ? e.message : "Failed to delete channel"); + } }; const handleTest = async (ch: Channel) => { @@ -188,6 +198,12 @@ export function ChannelsTab({ workspaceId }: Props) { + {error && ( +
+ {error} +
+ )} + {/* Create form */} {showForm && (
diff --git a/canvas/src/components/tabs/MemoryTab.tsx b/canvas/src/components/tabs/MemoryTab.tsx index 4502f982..fa70faa5 100644 --- a/canvas/src/components/tabs/MemoryTab.tsx +++ b/canvas/src/components/tabs/MemoryTab.tsx @@ -219,7 +219,7 @@ export function MemoryTab({ workspaceId }: Props) { Refresh