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