diff --git a/src/renderer/components/layout/AppShell.tsx b/src/renderer/components/layout/AppShell.tsx
new file mode 100644
index 0000000..bac7ebc
--- /dev/null
+++ b/src/renderer/components/layout/AppShell.tsx
@@ -0,0 +1,53 @@
+import { AppHeader } from './AppHeader';
+import { TabBar } from './TabBar';
+import { Toolbar } from './Toolbar';
+import { Breadcrumb } from './Breadcrumb';
+import { StatusBar } from './StatusBar';
+import { useAppStore } from '@/stores/app-store';
+import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@/components/ui/resizable';
+
+export function AppShell() {
+ const { sidebarVisible, previewVisible, paneSizes, setPaneSizes } = useAppStore();
+
+ return (
+
+
+
+
+
+
+ setPaneSizes({ sidebar: sizes[0], editor: sizes[1], preview: sizes[2] })}
+ >
+ {sidebarVisible && (
+ <>
+
+
+
+
+ >
+ )}
+
+
+
+ {previewVisible && (
+ <>
+
+
+
+
+ >
+ )}
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/renderer/components/ui/resizable.tsx b/src/renderer/components/ui/resizable.tsx
index dd28d33..a312351 100644
--- a/src/renderer/components/ui/resizable.tsx
+++ b/src/renderer/components/ui/resizable.tsx
@@ -7,10 +7,10 @@ import * as ResizablePrimitive from "react-resizable-panels"
import { cn } from "@/lib/utils"
const ResizablePanelGroup = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
))
-ResizablePanelGroup.displayName = ResizablePrimitive.PanelGroup.displayName
+ResizablePanelGroup.displayName = "ResizablePanelGroup"
const ResizablePanel = React.forwardRef<
React.ElementRef,
@@ -34,16 +34,16 @@ const ResizablePanel = React.forwardRef<
{...props}
/>
))
-ResizablePanel.displayName = ResizablePrimitive.Panel.displayName
+ResizablePanel.displayName = "ResizablePanel"
const ResizableHandle = ({
withHandle,
className,
...props
-}: React.ComponentPropsWithoutRef & {
+}: React.ComponentPropsWithoutRef & {
withHandle?: boolean
}) => (
- div]:rotate-90",
className
@@ -55,7 +55,7 @@ const ResizableHandle = ({
)}
-
+
)
ResizableHandle.displayName = "ResizableHandle"
diff --git a/src/renderer/test/setup.ts b/src/renderer/test/setup.ts
index 461543e..8be2518 100644
--- a/src/renderer/test/setup.ts
+++ b/src/renderer/test/setup.ts
@@ -27,3 +27,9 @@ if (typeof window !== 'undefined') {
}),
});
}
+
+// Mock react-resizable-panels for jsdom environment
+if (typeof window !== 'undefined') {
+ Object.defineProperty(window, 'innerWidth', { writable: true, value: 1920 });
+ Object.defineProperty(window, 'innerHeight', { writable: true, value: 1080 });
+}
diff --git a/tests/component/layout/AppShell.test.tsx b/tests/component/layout/AppShell.test.tsx
new file mode 100644
index 0000000..cd1cfe2
--- /dev/null
+++ b/tests/component/layout/AppShell.test.tsx
@@ -0,0 +1,49 @@
+import { describe, it, expect, beforeEach, vi } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import { ThemeProvider } from '@/components/theme-provider';
+import { AppShell } from '@/components/layout/AppShell';
+import { useAppStore } from '@/stores/app-store';
+
+// Mock react-resizable-panels for jsdom environment
+vi.mock('@/components/ui/resizable', () => ({
+ ResizablePanelGroup: ({ children, direction, onLayout }: any) => (
+
+ {children}
+
+ ),
+ ResizablePanel: ({ children, defaultSize, minSize, maxSize }: any) => (
+
+ {children}
+
+ ),
+ ResizableHandle: () => ,
+}));
+
+describe('AppShell', () => {
+ beforeEach(() => {
+ localStorage.clear();
+ useAppStore.setState({ sidebarVisible: true, previewVisible: true, zenMode: false });
+ });
+
+ it('renders all shell surfaces when sidebar and preview are visible', () => {
+ render(
+
+
+
+ );
+ expect(screen.getByText(/markdownconverter/i)).toBeInTheDocument();
+ expect(screen.getByText(/no files open/i)).toBeInTheDocument();
+ expect(screen.getByText(/no file selected/i)).toBeInTheDocument();
+ expect(screen.getByText(/0 words/i)).toBeInTheDocument();
+ });
+
+ it('hides sidebar when sidebarVisible is false', () => {
+ useAppStore.setState({ sidebarVisible: false });
+ render(
+
+
+
+ );
+ expect(screen.queryByText(/file tree placeholder/i)).not.toBeInTheDocument();
+ });
+});
\ No newline at end of file