From 97bdc6a8ec64f39afc7cdc56600c3906575ff99d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 22 Dec 2025 09:41:58 +0000 Subject: [PATCH 1/6] chore: add empty vnext-shell.patch file --- vnext-shell.patch | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 vnext-shell.patch diff --git a/vnext-shell.patch b/vnext-shell.patch new file mode 100644 index 0000000..e69de29 From 99c47a474b942849043361f7f8cec7a09dc1bcdf Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 22 Dec 2025 09:48:43 +0000 Subject: [PATCH 2/6] feat: implement vNext shell UI architecture This commit introduces a new modular UI architecture for Blendmate: - Refactor App.tsx to be a plumbing wrapper only - Create AppShell.tsx as the main vNext layout container - Add modular layout components: - TopBar: Header with socket status and layout controls - CenterWorkspace: Main workspace area - RightInspector: Contextual help panel - BottomBar: Command input and message display - Add useLayoutStore hook for persisting UI state to localStorage - Include vnext-shell.patch file with the original patch content This establishes a clean baseline for adding tabs, panels, and workflows on top of the vNext shell. --- blendmate-app/src/App.tsx | 71 +--- blendmate-app/src/app/AppShell.tsx | 69 ++++ blendmate-app/src/app/layout/BottomBar.tsx | 60 +++ .../src/app/layout/CenterWorkspace.tsx | 23 ++ .../src/app/layout/RightInspector.tsx | 15 + blendmate-app/src/app/layout/TopBar.tsx | 63 ++++ blendmate-app/src/state/layoutStore.ts | 47 +++ vnext-shell.patch | 355 ++++++++++++++++++ 8 files changed, 643 insertions(+), 60 deletions(-) create mode 100644 blendmate-app/src/app/AppShell.tsx create mode 100644 blendmate-app/src/app/layout/BottomBar.tsx create mode 100644 blendmate-app/src/app/layout/CenterWorkspace.tsx create mode 100644 blendmate-app/src/app/layout/RightInspector.tsx create mode 100644 blendmate-app/src/app/layout/TopBar.tsx create mode 100644 blendmate-app/src/state/layoutStore.ts diff --git a/blendmate-app/src/App.tsx b/blendmate-app/src/App.tsx index 15cdd9c..6540579 100644 --- a/blendmate-app/src/App.tsx +++ b/blendmate-app/src/App.tsx @@ -1,68 +1,19 @@ -import { useState, useEffect } from "react"; +import React from "react"; import { useBlendmateSocket } from "./useBlendmateSocket"; -import { usePanelManager } from "./usePanelManager"; -import { LoggedEvent } from "./types/panels"; -import HUD from "./components/layout/HUD"; -import Footer from "./components/layout/Footer"; -import SandboxLayout from "./components/layout/SandboxLayout"; +import { AppShell } from "./app/AppShell"; +/** + * App = plumbing wrapper only. + * UI layout lives in src/app/AppShell.tsx (vNext). + */ export default function App() { const { status, lastMessage, sendJson } = useBlendmateSocket(); - const { panelStates, togglePanel, visiblePanels } = usePanelManager(); - - const [frame, setFrame] = useState(1); - const [currentNodeId, setCurrentNodeId] = useState('GeometryNodeInstanceOnPoints'); - const [events, setEvents] = useState([]); - - // React to incoming context messages from Blender - // Rule 1 (UI_RULES.md): No auto-opening panels on events - useEffect(() => { - if (lastMessage) { - // Add to events log with size limit to prevent memory issues - setEvents((prev: LoggedEvent[]) => { - const newEvents = [...prev, { - type: lastMessage.type as string, - timestamp: Date.now(), - data: lastMessage - }]; - // Keep only last 100 events - return newEvents.slice(-100); - }); - - if (lastMessage.type === 'context' && lastMessage.node_id) { - setCurrentNodeId(lastMessage.node_id as string); - // Quiet companion rule: do not auto-open or focus panels on context changes - } else if (lastMessage.type === 'event' && lastMessage.event === 'frame_change') { - setFrame(lastMessage.frame as number); - } - } - }, [lastMessage, togglePanel]); return ( -
- - - {/* Use sandbox layout for interactive UI exploration */} - - -
- - -
+ ); } diff --git a/blendmate-app/src/app/AppShell.tsx b/blendmate-app/src/app/AppShell.tsx new file mode 100644 index 0000000..2c96c44 --- /dev/null +++ b/blendmate-app/src/app/AppShell.tsx @@ -0,0 +1,69 @@ +import React, { useEffect } from "react"; +import { TopBar } from "./layout/TopBar"; +import { CenterWorkspace } from "./layout/CenterWorkspace"; +import { RightInspector } from "./layout/RightInspector"; +import { BottomBar } from "./layout/BottomBar"; +import { useLayoutStore } from "../state/layoutStore"; + +export type SocketStatus = "connecting" | "connected" | "disconnected" | string; + +export type AppShellProps = { + socketStatus: SocketStatus; + lastMessage: any; + sendJson: (json: any) => void; +}; + +export function AppShell(props: AppShellProps) { + const layout = useLayoutStore(); + + useEffect(() => { + // Sanity marker: guarantees you're seeing the vNext shell build. + // eslint-disable-next-line no-console + console.info("Blendmate UI vNext Shell mounted"); + }, []); + + return ( +
+ + +
+
+ +
+ + {layout.inspectorOpen ? ( +
+ +
+ ) : null} +
+ + {layout.bottomOpen ? ( +
+ +
+ ) : ( +
+ Bottom bar hidden + +
+ )} +
+ ); +} diff --git a/blendmate-app/src/app/layout/BottomBar.tsx b/blendmate-app/src/app/layout/BottomBar.tsx new file mode 100644 index 0000000..93ee123 --- /dev/null +++ b/blendmate-app/src/app/layout/BottomBar.tsx @@ -0,0 +1,60 @@ +import React, { useMemo, useState } from "react"; + +export type BottomBarProps = { + lastMessage: any; + sendJson: (json: any) => void; + onToggleBottom: () => void; + vnextBadge?: boolean; +}; + +export function BottomBar(props: BottomBarProps) { + const [prompt, setPrompt] = useState(""); + + const last = useMemo(() => { + if (!props.lastMessage) return "Awaiting Blender signal…"; + try { + return "[RECV] " + JSON.stringify(props.lastMessage); + } catch { + return "[RECV] (unserializable message)"; + } + }, [props.lastMessage]); + + return ( +
+ {props.vnextBadge ? ( + vNext + ) : null} + + + +
+ setPrompt(e.target.value)} + /> +
+ + + +
+ {last} +
+
+ ); +} diff --git a/blendmate-app/src/app/layout/CenterWorkspace.tsx b/blendmate-app/src/app/layout/CenterWorkspace.tsx new file mode 100644 index 0000000..59920b1 --- /dev/null +++ b/blendmate-app/src/app/layout/CenterWorkspace.tsx @@ -0,0 +1,23 @@ +import React from "react"; + +export function CenterWorkspace() { + return ( +
+
+
Workspace
+
tabs/splits later
+
+ +
+
+
+
Sandbox canvas
+
+ This is the vNext shell baseline. We'll add tabs, panels, and workflows on top of this. +
+
+
+
+
+ ); +} diff --git a/blendmate-app/src/app/layout/RightInspector.tsx b/blendmate-app/src/app/layout/RightInspector.tsx new file mode 100644 index 0000000..951caa1 --- /dev/null +++ b/blendmate-app/src/app/layout/RightInspector.tsx @@ -0,0 +1,15 @@ +import React from "react"; + +export function RightInspector() { + return ( +
+
Inspector
+
+
Nothing selected
+
+ Contextual help will appear here (tools, nodes, selection mode, shortcuts…). +
+
+
+ ); +} diff --git a/blendmate-app/src/app/layout/TopBar.tsx b/blendmate-app/src/app/layout/TopBar.tsx new file mode 100644 index 0000000..3d9b197 --- /dev/null +++ b/blendmate-app/src/app/layout/TopBar.tsx @@ -0,0 +1,63 @@ +import React from "react"; +import type { SocketStatus } from "../AppShell"; + +export type TopBarProps = { + socketStatus: SocketStatus; + inspectorOpen: boolean; + onToggleInspector: () => void; + onResetLayout: () => void; +}; + +function statusLabel(s: SocketStatus) { + if (s === "connected") return "Connected"; + if (s === "connecting") return "Connecting…"; + if (s === "disconnected") return "Disconnected"; + return String(s || "Unknown"); +} + +export function TopBar(props: TopBarProps) { + const connected = props.socketStatus === "connected"; + + return ( +
+
+
+ Blendmate +
+ + vNext Shell + +
+ +
+
+ + {statusLabel(props.socketStatus)} +
+ + + + +
+
+ ); +} diff --git a/blendmate-app/src/state/layoutStore.ts b/blendmate-app/src/state/layoutStore.ts new file mode 100644 index 0000000..1e709fd --- /dev/null +++ b/blendmate-app/src/state/layoutStore.ts @@ -0,0 +1,47 @@ +import { useEffect, useMemo, useState } from "react"; + +type LayoutState = { + inspectorOpen: boolean; + bottomOpen: boolean; +}; + +const KEY = "blendmate.layout.vnext"; + +function readInitial(): LayoutState { + try { + const raw = localStorage.getItem(KEY); + if (!raw) return { inspectorOpen: true, bottomOpen: true }; + const parsed = JSON.parse(raw); + return { + inspectorOpen: Boolean(parsed.inspectorOpen), + bottomOpen: Boolean(parsed.bottomOpen), + }; + } catch { + return { inspectorOpen: true, bottomOpen: true }; + } +} + +export function useLayoutStore() { + const [state, setState] = useState(() => readInitial()); + + useEffect(() => { + try { + localStorage.setItem(KEY, JSON.stringify(state)); + } catch { + // ignore + } + }, [state]); + + return useMemo( + () => ({ + inspectorOpen: state.inspectorOpen, + bottomOpen: state.bottomOpen, + toggleInspector: () => + setState((s) => ({ ...s, inspectorOpen: !s.inspectorOpen })), + toggleBottom: () => + setState((s) => ({ ...s, bottomOpen: !s.bottomOpen })), + resetLayout: () => setState({ inspectorOpen: true, bottomOpen: true }), + }), + [state] + ); +} diff --git a/vnext-shell.patch b/vnext-shell.patch index e69de29..1f4a981 100644 --- a/vnext-shell.patch +++ b/vnext-shell.patch @@ -0,0 +1,355 @@ +diff --git a/blendmate-app/src/App.tsx b/blendmate-app/src/App.tsx +index 1111111..2222222 100644 +--- a/blendmate-app/src/App.tsx ++++ b/blendmate-app/src/App.tsx +@@ -1,200 +1,34 @@ +-import React, { useEffect, useMemo, useState } from "react"; +-import HUD from "./components/layout/HUD"; +-import Footer from "./components/layout/Footer"; +-import SandboxLayout from "./components/layout/SandboxLayout"; +-import { useBlendmateSocket } from "./useBlendmateSocket"; +-import { usePanelManager } from "./usePanelManager"; +- +-export default function App() { +- // legacy/test orchestration... +- // (previous sandbox layout and event/state wiring) +- return ( +-
+- +- +-
+-
+- ); +-} ++import React from "react"; ++import { useBlendmateSocket } from "./useBlendmateSocket"; ++import { AppShell } from "./app/AppShell"; ++ ++/** ++ * App = plumbing wrapper only. ++ * UI layout lives in src/app/AppShell.tsx (vNext). ++ */ ++export default function App() { ++ const { status, lastMessage, sendJson } = useBlendmateSocket(); ++ ++ return ( ++ ++ ); ++} +diff --git a/blendmate-app/src/app/AppShell.tsx b/blendmate-app/src/app/AppShell.tsx +new file mode 100644 +index 0000000..3333333 +--- /dev/null ++++ b/blendmate-app/src/app/AppShell.tsx +@@ -0,0 +1,96 @@ ++import React, { useEffect } from "react"; ++import { TopBar } from "./layout/TopBar"; ++import { CenterWorkspace } from "./layout/CenterWorkspace"; ++import { RightInspector } from "./layout/RightInspector"; ++import { BottomBar } from "./layout/BottomBar"; ++import { useLayoutStore } from "../state/layoutStore"; ++ ++export type SocketStatus = "connecting" | "connected" | "disconnected" | string; ++ ++export type AppShellProps = { ++ socketStatus: SocketStatus; ++ lastMessage: any; ++ sendJson: (json: any) => void; ++}; ++ ++export function AppShell(props: AppShellProps) { ++ const layout = useLayoutStore(); ++ ++ useEffect(() => { ++ // Sanity marker: guarantees you're seeing the vNext shell build. ++ // eslint-disable-next-line no-console ++ console.info("Blendmate UI vNext Shell mounted"); ++ }, []); ++ ++ return ( ++
++ ++ ++
++
++ ++
++ ++ {layout.inspectorOpen ? ( ++
++ ++
++ ) : null} ++
++ ++ {layout.bottomOpen ? ( ++
++ ++
++ ) : ( ++
++ Bottom bar hidden ++ ++
++ )} ++
++ ); ++} +diff --git a/blendmate-app/src/app/layout/TopBar.tsx b/blendmate-app/src/app/layout/TopBar.tsx +new file mode 100644 +index 0000000..4444444 +--- /dev/null ++++ b/blendmate-app/src/app/layout/TopBar.tsx +@@ -0,0 +1,76 @@ ++import React from "react"; ++import type { SocketStatus } from "../AppShell"; ++ ++export type TopBarProps = { ++ socketStatus: SocketStatus; ++ inspectorOpen: boolean; ++ onToggleInspector: () => void; ++ onResetLayout: () => void; ++}; ++ ++function statusLabel(s: SocketStatus) { ++ if (s === "connected") return "Connected"; ++ if (s === "connecting") return "Connecting&"; ++ if (s === "disconnected") return "Disconnected"; ++ return String(s || "Unknown"); ++} ++ ++export function TopBar(props: TopBarProps) { ++ const connected = props.socketStatus === "connected"; ++ ++ return ( ++
++
++
++ Blendmate ++
++ ++ vNext Shell ++ ++
++ ++
++
++ ++ {statusLabel(props.socketStatus)} ++
++ ++ ++ ++ ++
++
++ ); ++} +diff --git a/blendmate-app/src/app/layout/CenterWorkspace.tsx b/blendmate-app/src/app/layout/CenterWorkspace.tsx +new file mode 100644 +index 0000000..5555555 +--- /dev/null ++++ b/blendmate-app/src/app/layout/CenterWorkspace.tsx +@@ -0,0 +1,42 @@ ++import React from "react"; ++ ++export function CenterWorkspace() { ++ return ( ++
++
++
Workspace
++
tabs/splits later
++
++ ++
++
++
++
Sandbox canvas
++
++ This is the vNext shell baseline. We'll add tabs, panels, and workflows on top of this. ++
++
++
++
++
++ ); ++} +diff --git a/blendmate-app/src/app/layout/RightInspector.tsx b/blendmate-app/src/app/layout/RightInspector.tsx +new file mode 100644 +index 0000000..6666666 +--- /dev/null ++++ b/blendmate-app/src/app/layout/RightInspector.tsx +@@ -0,0 +1,33 @@ ++import React from "react"; ++ ++export function RightInspector() { ++ return ( ++
++
Inspector
++
++
Nothing selected
++
++ Contextual help will appear here (tools, nodes, selection mode, shortcuts&). ++
++
++
++ ); ++} +diff --git a/blendmate-app/src/app/layout/BottomBar.tsx b/blendmate-app/src/app/layout/BottomBar.tsx +new file mode 100644 +index 0000000..7777777 +--- /dev/null ++++ b/blendmate-app/src/app/layout/BottomBar.tsx +@@ -0,0 +1,79 @@ ++import React, { useMemo, useState } from "react"; ++ ++export type BottomBarProps = { ++ lastMessage: any; ++ sendJson: (json: any) => void; ++ onToggleBottom: () => void; ++ vnextBadge?: boolean; ++}; ++ ++export function BottomBar(props: BottomBarProps) { ++ const [prompt, setPrompt] = useState(""); ++ ++ const last = useMemo(() => { ++ if (!props.lastMessage) return "Awaiting Blender signal&"; ++ try { ++ return "[RECV] " + JSON.stringify(props.lastMessage); ++ } catch { ++ return "[RECV] (unserializable message)"; ++ } ++ }, [props.lastMessage]); ++ ++ return ( ++
++ {props.vnextBadge ? ( ++ vNext ++ ) : null} ++ ++ ++ ++
++ setPrompt(e.target.value)} ++ /> ++
++ ++ ++ ++
++ {last} ++
++
++ ); ++} +diff --git a/blendmate-app/src/state/layoutStore.ts b/blendmate-app/src/state/layoutStore.ts +new file mode 100644 +index 0000000..8888888 +--- /dev/null ++++ b/blendmate-app/src/state/layoutStore.ts +@@ -0,0 +1,71 @@ ++import { useEffect, useMemo, useState } from "react"; ++ ++type LayoutState = { ++ inspectorOpen: boolean; ++ bottomOpen: boolean; ++}; ++ ++const KEY = "blendmate.layout.vnext"; ++ ++function readInitial(): LayoutState { ++ try { ++ const raw = localStorage.getItem(KEY); ++ if (!raw) return { inspectorOpen: true, bottomOpen: true }; ++ const parsed = JSON.parse(raw); ++ return { ++ inspectorOpen: Boolean(parsed.inspectorOpen), ++ bottomOpen: Boolean(parsed.bottomOpen), ++ }; ++ } catch { ++ return { inspectorOpen: true, bottomOpen: true }; ++ } ++} ++ ++export function useLayoutStore() { ++ const [state, setState] = useState(() => readInitial()); ++ ++ useEffect(() => { ++ try { ++ localStorage.setItem(KEY, JSON.stringify(state)); ++ } catch { ++ // ignore ++ } ++ }, [state]); ++ ++ return useMemo( ++ () => ({ ++ inspectorOpen: state.inspectorOpen, ++ bottomOpen: state.bottomOpen, ++ toggleInspector: () => ++ setState((s) => ({ ...s, inspectorOpen: !s.inspectorOpen })), ++ toggleBottom: () => ++ setState((s) => ({ ...s, bottomOpen: !s.bottomOpen })), ++ resetLayout: () => setState({ inspectorOpen: true, bottomOpen: true }), ++ }), ++ [state] ++ ); ++} From a0d2002c44e487f8aaf624182c2d11f4d404d81b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Lebdu=C5=A1ka?= Date: Thu, 25 Dec 2025 19:03:05 +0100 Subject: [PATCH 3/6] refactor(app): add component barrels and use them in App --- blendmate-app/src/App.tsx | 115 ++++++++++++++++++++--- blendmate-app/src/components/index.ts | 6 ++ blendmate-app/src/components/ui/index.ts | 11 +++ 3 files changed, 120 insertions(+), 12 deletions(-) create mode 100644 blendmate-app/src/components/index.ts create mode 100644 blendmate-app/src/components/ui/index.ts diff --git a/blendmate-app/src/App.tsx b/blendmate-app/src/App.tsx index 6540579..6858d06 100644 --- a/blendmate-app/src/App.tsx +++ b/blendmate-app/src/App.tsx @@ -1,19 +1,110 @@ -import React from "react"; +import { useState, useEffect } from "react"; import { useBlendmateSocket } from "./useBlendmateSocket"; -import { AppShell } from "./app/AppShell"; +import { Outliner, NodeHelpView, Panels, } from "@/components"; +import { EventsLogPanel } from "@/components/panels"; +import { Activity, LayoutGrid, Info, ListTree } from "lucide-react"; +import BackgroundPaths from "@/components/ui/BackgroundPaths"; +import { ResizableHandle, ResizablePanel, ResizablePanelGroup, Card, ScrollArea } from "@/components/ui"; + +// Color tokens (increased opacity for debug visibility) -/** - * App = plumbing wrapper only. - * UI layout lives in src/app/AppShell.tsx (vNext). - */ export default function App() { - const { status, lastMessage, sendJson } = useBlendmateSocket(); + const { lastMessage, status } = useBlendmateSocket(); + const [currentNodeId, setCurrentNodeId] = useState('GeometryNodeInstanceOnPoints'); + + const wsColor = status === 'connected' ? 'bg-emerald-500 animate-pulse' : 'bg-orange-500'; + const wsLabel = status === 'connected' ? 'connected' : 'disconnected'; + + useEffect(() => { + if (lastMessage) { + if ((lastMessage as any).type === 'context' && (lastMessage as any).node_id) { + setCurrentNodeId((lastMessage as any).node_id as string); + } + } + }, [lastMessage]); return ( - +
+ {/* Base background layer */} +
+ + {/* App content (stack above beams) */} +
+
+ + {/* Left Island: Outliner + Events */} + +
+ +
+ + Outliner +
+ + + +
+ + + + +
+
+ + + + {/* Center Island: Bench */} + + +
+
+ + Bench +
+
+
+ +
+
+ +
+
+ + {/* Mini Footer / Status */} +
+
+ BLENDMATE v0.1.0 + + + 60 FPS + +
+
+
); } diff --git a/blendmate-app/src/components/index.ts b/blendmate-app/src/components/index.ts new file mode 100644 index 0000000..56eec38 --- /dev/null +++ b/blendmate-app/src/components/index.ts @@ -0,0 +1,6 @@ +// Barrel file for components +export { default as Outliner } from "./Outliner"; +export { default as NodeHelpView } from "./NodeHelpView"; +export * as Panels from "./panels"; +export * from "./ui"; + diff --git a/blendmate-app/src/components/ui/index.ts b/blendmate-app/src/components/ui/index.ts new file mode 100644 index 0000000..fb0c4c9 --- /dev/null +++ b/blendmate-app/src/components/ui/index.ts @@ -0,0 +1,11 @@ +// Barrel file for ui components +export { BackgroundPaths, BackgroundPathsProps } from "./BackgroundPaths"; +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent, CardAction } from "./Card"; +export { default as IslandPanel } from "./IslandPanel"; +export { ScrollArea, ScrollBar } from "./scroll-area"; +export { ResizablePanelGroup as ResizablePanelGroup, ResizablePanel as ResizablePanel, ResizableHandle as ResizableHandle } from "./resizable"; +export { default as Badge } from "./badge"; +export { default as Button } from "./button"; +export { default as Input } from "./input"; +export { default as Skeleton } from "./skeleton"; + From deebaf737e8611446fa2909b6d049e6f7cf4acb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Lebdu=C5=A1ka?= Date: Thu, 25 Dec 2025 19:04:44 +0100 Subject: [PATCH 4/6] chore(ui): fix ui barrel exports and add panels barrel --- blendmate-app/src/components/panels/index.ts | 5 +++++ blendmate-app/src/components/ui/index.ts | 14 +++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 blendmate-app/src/components/panels/index.ts diff --git a/blendmate-app/src/components/panels/index.ts b/blendmate-app/src/components/panels/index.ts new file mode 100644 index 0000000..bc2587d --- /dev/null +++ b/blendmate-app/src/components/panels/index.ts @@ -0,0 +1,5 @@ +export { default as ChatPanel } from "./ChatPanel"; +export { default as EventsLogPanel } from "./EventsLogPanel"; +export { default as NodesHelpPanel } from "./NodesHelpPanel"; +export { default as StatsPanel } from "./StatsPanel"; + diff --git a/blendmate-app/src/components/ui/index.ts b/blendmate-app/src/components/ui/index.ts index fb0c4c9..4f19158 100644 --- a/blendmate-app/src/components/ui/index.ts +++ b/blendmate-app/src/components/ui/index.ts @@ -1,11 +1,11 @@ // Barrel file for ui components -export { BackgroundPaths, BackgroundPathsProps } from "./BackgroundPaths"; +export { BackgroundPaths } from "./BackgroundPaths"; +export type { BackgroundPathsProps } from "./BackgroundPaths"; export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent, CardAction } from "./Card"; export { default as IslandPanel } from "./IslandPanel"; export { ScrollArea, ScrollBar } from "./scroll-area"; -export { ResizablePanelGroup as ResizablePanelGroup, ResizablePanel as ResizablePanel, ResizableHandle as ResizableHandle } from "./resizable"; -export { default as Badge } from "./badge"; -export { default as Button } from "./button"; -export { default as Input } from "./input"; -export { default as Skeleton } from "./skeleton"; - +export { ResizablePanelGroup, ResizablePanel, ResizableHandle } from "./resizable"; +export { Badge } from "./badge"; +export { Button } from "./button"; +export { Input } from "./input"; +export { Skeleton } from "./skeleton"; From 60296dce968fddc349704d60788908d445b7e250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Lebdu=C5=A1ka?= Date: Thu, 25 Dec 2025 19:05:20 +0100 Subject: [PATCH 5/6] refactor(app): remove unused Panels import from App --- blendmate-app/src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blendmate-app/src/App.tsx b/blendmate-app/src/App.tsx index 6858d06..4c42f9b 100644 --- a/blendmate-app/src/App.tsx +++ b/blendmate-app/src/App.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from "react"; import { useBlendmateSocket } from "./useBlendmateSocket"; -import { Outliner, NodeHelpView, Panels, } from "@/components"; +import { Outliner, NodeHelpView } from "@/components"; import { EventsLogPanel } from "@/components/panels"; import { Activity, LayoutGrid, Info, ListTree } from "lucide-react"; import BackgroundPaths from "@/components/ui/BackgroundPaths"; From 252b8781eb41fc43fc13abbf9d7d0fc558f3a172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Lebdu=C5=A1ka?= Date: Thu, 25 Dec 2025 19:07:46 +0100 Subject: [PATCH 6/6] refactor(components): remove unused React imports from multiple files --- blendmate-app/src/app/AppShell.tsx | 2 +- blendmate-app/src/app/layout/BottomBar.tsx | 2 +- .../src/app/layout/CenterWorkspace.tsx | 2 - .../src/app/layout/RightInspector.tsx | 2 - blendmate-app/src/app/layout/TopBar.tsx | 1 - blendmate-app/src/components/index.ts | 1 - .../components/ui/BackgroundPaths_Backup.tsx | 159 ------------------ .../src/components/ui/IslandPanel.tsx | 2 +- .../ui/__tests__/BackgroundPaths.test.tsx | 45 ----- 9 files changed, 3 insertions(+), 213 deletions(-) delete mode 100644 blendmate-app/src/components/ui/BackgroundPaths_Backup.tsx delete mode 100644 blendmate-app/src/components/ui/__tests__/BackgroundPaths.test.tsx diff --git a/blendmate-app/src/app/AppShell.tsx b/blendmate-app/src/app/AppShell.tsx index 2c96c44..8465f7c 100644 --- a/blendmate-app/src/app/AppShell.tsx +++ b/blendmate-app/src/app/AppShell.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import { useEffect } from "react"; import { TopBar } from "./layout/TopBar"; import { CenterWorkspace } from "./layout/CenterWorkspace"; import { RightInspector } from "./layout/RightInspector"; diff --git a/blendmate-app/src/app/layout/BottomBar.tsx b/blendmate-app/src/app/layout/BottomBar.tsx index 93ee123..9984fa3 100644 --- a/blendmate-app/src/app/layout/BottomBar.tsx +++ b/blendmate-app/src/app/layout/BottomBar.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState } from "react"; +import { useMemo, useState } from "react"; export type BottomBarProps = { lastMessage: any; diff --git a/blendmate-app/src/app/layout/CenterWorkspace.tsx b/blendmate-app/src/app/layout/CenterWorkspace.tsx index 59920b1..024ca37 100644 --- a/blendmate-app/src/app/layout/CenterWorkspace.tsx +++ b/blendmate-app/src/app/layout/CenterWorkspace.tsx @@ -1,5 +1,3 @@ -import React from "react"; - export function CenterWorkspace() { return (
diff --git a/blendmate-app/src/app/layout/RightInspector.tsx b/blendmate-app/src/app/layout/RightInspector.tsx index 951caa1..6e925c4 100644 --- a/blendmate-app/src/app/layout/RightInspector.tsx +++ b/blendmate-app/src/app/layout/RightInspector.tsx @@ -1,5 +1,3 @@ -import React from "react"; - export function RightInspector() { return (
diff --git a/blendmate-app/src/app/layout/TopBar.tsx b/blendmate-app/src/app/layout/TopBar.tsx index 3d9b197..7d54fa3 100644 --- a/blendmate-app/src/app/layout/TopBar.tsx +++ b/blendmate-app/src/app/layout/TopBar.tsx @@ -1,4 +1,3 @@ -import React from "react"; import type { SocketStatus } from "../AppShell"; export type TopBarProps = { diff --git a/blendmate-app/src/components/index.ts b/blendmate-app/src/components/index.ts index 56eec38..ec953a8 100644 --- a/blendmate-app/src/components/index.ts +++ b/blendmate-app/src/components/index.ts @@ -1,6 +1,5 @@ // Barrel file for components export { default as Outliner } from "./Outliner"; export { default as NodeHelpView } from "./NodeHelpView"; -export * as Panels from "./panels"; export * from "./ui"; diff --git a/blendmate-app/src/components/ui/BackgroundPaths_Backup.tsx b/blendmate-app/src/components/ui/BackgroundPaths_Backup.tsx deleted file mode 100644 index 99aec48..0000000 --- a/blendmate-app/src/components/ui/BackgroundPaths_Backup.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import { useMemo, memo } from "react"; - -interface BackgroundPathsProps { - color?: string; - className?: string; -} - -function BackgroundPathsComponent({ color = "rgba(99,102,241,0.18)", className = "" }: BackgroundPathsProps) { - // Procedurally generate multiple layers of paths with slight randomness and different animations - const width = 1400; - const height = 900; - - // simple deterministic PRNG with better distribution - function makeRng(seed: number) { - let s = seed % 2147483647; - if (s <= 0) s += 2147483646; - return () => { - s = (s * 48271) % 2147483647; // standard Lehmer generator - return (s - 1) / 2147483646; - }; - } - - // Build a multi-segment wavy cubic path between startX and endX. - function makeWavyPath(startX: number, yStart: number, endX: number, yEnd: number, rng: () => number, amp: number, segments = 5) { - const span = endX - startX; - const pts: Array<{ x: number; y: number }> = []; - pts.push({ x: startX, y: yStart }); - - for (let s = 1; s < segments; s++) { - const t = s / segments; - const x = startX + span * t; - const yBase = yStart + (yEnd - yStart) * t; - // Use simpler wave generation to reduce complexity - const wave = Math.sin(t * Math.PI * 2 + rng() * Math.PI) * amp; - const jitter = (rng() - 0.5) * amp * 0.2; - pts.push({ x, y: yBase + wave + jitter }); - } - pts.push({ x: endX, y: yEnd }); - - let d = `M${pts[0].x.toFixed(1)},${pts[0].y.toFixed(1)}`; - for (let i = 0; i < pts.length - 1; i++) { - const p0 = pts[i]; - const p1 = pts[i + 1]; - const dx = p1.x - p0.x; - const cp1x = (p0.x + dx * 0.38).toFixed(1); - const cp1y = (p0.y + (rng() - 0.5) * amp * 0.5).toFixed(1); - const cp2x = (p0.x + dx * 0.62).toFixed(1); - const cp2y = (p1.y + (rng() - 0.5) * amp * 0.5).toFixed(1); - d += ` C ${cp1x},${cp1y} ${cp2x},${cp2y} ${p1.x.toFixed(1)},${p1.y.toFixed(1)}`; - } - return d; - } - - const layerPaths = useMemo(() => { - const layers = [ - { count: 22, strokeWidth: 1, opacity: 0.8, anim: 'bm-anim-slow', amp: 180, blur: true }, - { count: 12, strokeWidth: 1, opacity: 0.9, anim: 'bm-anim-med', amp: 120 }, - { count: 43, strokeWidth: 1, opacity: 0.7, anim: 'bm-anim-fast', amp: 90 }, - ]; - - return layers.map((layer, li) => { - const rng = makeRng(2000 + li * 137); - const paths: Array<{ d: string; delay: string; strokeWidth: number; opacity: number; duration: string }> = []; - const baseY = height * 0.5; - - for (let i = 0; i < layer.count; i++) { - const t = i / Math.max(1, layer.count - 1); - const yStart = baseY + (t - 0.5) * 600 + (rng() - 0.5) * 100; - const yEnd = yStart + (rng() - 0.5) * 150; - - const EXTENT = width * 1.5; - const startX = -EXTENT; - const endX = width + EXTENT; - - const segments = Math.max(3, Math.min(5, Math.round(layer.amp / 40))); - const d = makeWavyPath(startX, yStart, endX, yEnd, rng, layer.amp, segments); - - const delay = `${-(rng() * 60).toFixed(2)}s`; - const baseDur = layer.anim === 'bm-anim-slow' ? 60 : layer.anim === 'bm-anim-med' ? 45 : 30; - const dur = baseDur * (0.8 + rng() * 0.4); - const duration = `${dur.toFixed(2)}s`; - - const sw = layer.strokeWidth * (0.8 + rng() * 0.4); - const op = layer.opacity * (0.8 + rng() * 0.4); - paths.push({ d, delay, duration, strokeWidth: Number(sw.toFixed(1)), opacity: Number(op.toFixed(3)) }); - } - return { layer, paths }; - }); - }, []); - - return ( -
- - - - - - - - - - - - - {layerPaths.map(({ layer, paths }, li) => ( - - {paths.map((p, pi) => ( - - ))} - - ))} - -
- ); -} - -const BackgroundPaths = memo(BackgroundPathsComponent); -export default BackgroundPaths; diff --git a/blendmate-app/src/components/ui/IslandPanel.tsx b/blendmate-app/src/components/ui/IslandPanel.tsx index 3f20e03..b5faa0f 100644 --- a/blendmate-app/src/components/ui/IslandPanel.tsx +++ b/blendmate-app/src/components/ui/IslandPanel.tsx @@ -1,5 +1,5 @@ import { ReactNode } from 'react'; -import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardAction } from "@/components/ui/card"; +import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardAction } from "@/components/ui/Card"; import { cn } from "@/lib/utils"; interface IslandPanelProps { diff --git a/blendmate-app/src/components/ui/__tests__/BackgroundPaths.test.tsx b/blendmate-app/src/components/ui/__tests__/BackgroundPaths.test.tsx deleted file mode 100644 index f69e1ac..0000000 --- a/blendmate-app/src/components/ui/__tests__/BackgroundPaths.test.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { render } from '@testing-library/react'; -import { describe, it, beforeEach, afterEach, expect, vi } from 'vitest'; -import BackgroundPaths from '../BackgroundPaths'; - -// Mock Math.random to be deterministic for test -const randomValues: number[] = []; -let rvIndex = 0; -const seededRandom = () => { - if (rvIndex >= randomValues.length) rvIndex = 0; - return randomValues[rvIndex++]; -}; - -describe('BackgroundPaths', () => { - beforeEach(() => { - // deterministic sequence: alternate values to produce different x/y/props - randomValues.length = 0; - randomValues.push(0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9); - rvIndex = 0; - vi.spyOn(Math, 'random').mockImplementation(seededRandom as any); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); - - it('renders expected number of streak groups and paths with quadratic bezier', () => { - const count = 6; - render(); - - // čekáme count elementů - const groups = document.querySelectorAll('g[data-debug="true"]'); - expect(groups.length).toBe(count); - - // Každá skupina by měla obsahovat s atributem stroke a křivkou Q - groups.forEach((g) => { - const path = g.querySelector('path'); - expect(path).toBeTruthy(); - if (path) { - const d = path.getAttribute('d') || ''; - expect(d.includes('Q')).toBe(true); - expect(path.getAttribute('stroke')).toBe('rgb(255,0,0)'); - } - }); - }); -});