diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 3fafb59d..27a543ca 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -16,6 +16,8 @@
"title": "Laputa",
"width": 1400,
"height": 900,
+ "minWidth": 800,
+ "minHeight": 400,
"resizable": true,
"fullscreen": false,
"titleBarStyle": "Overlay",
diff --git a/src/App.css b/src/App.css
index 71c33ce1..de568d63 100644
--- a/src/App.css
+++ b/src/App.css
@@ -16,7 +16,8 @@
}
.app__sidebar {
- flex-shrink: 0;
+ flex-shrink: 1;
+ min-width: 180px;
display: flex;
flex-direction: column;
}
@@ -26,7 +27,8 @@
}
.app__note-list {
- flex-shrink: 0;
+ flex-shrink: 10;
+ min-width: 220px;
display: flex;
flex-direction: column;
}
@@ -37,7 +39,7 @@
.app__editor {
flex: 1;
- min-width: 0;
+ min-width: 400px;
min-height: 0;
overflow: hidden;
display: flex;
diff --git a/src/components/EditorRightPanel.tsx b/src/components/EditorRightPanel.tsx
index 68153b13..2dd8bf0d 100644
--- a/src/components/EditorRightPanel.tsx
+++ b/src/components/EditorRightPanel.tsx
@@ -40,7 +40,7 @@ export function EditorRightPanel({
return (
onToggleAIChat?.()}
diff --git a/src/hooks/useLayoutPanels.test.ts b/src/hooks/useLayoutPanels.test.ts
new file mode 100644
index 00000000..9aae1c9a
--- /dev/null
+++ b/src/hooks/useLayoutPanels.test.ts
@@ -0,0 +1,55 @@
+import { describe, it, expect } from 'vitest'
+import { renderHook, act } from '@testing-library/react'
+import { useLayoutPanels, COLUMN_MIN_WIDTHS } from './useLayoutPanels'
+
+describe('useLayoutPanels', () => {
+ it('exports column minimum widths', () => {
+ expect(COLUMN_MIN_WIDTHS.sidebar).toBe(180)
+ expect(COLUMN_MIN_WIDTHS.noteList).toBe(220)
+ expect(COLUMN_MIN_WIDTHS.editor).toBe(400)
+ expect(COLUMN_MIN_WIDTHS.inspector).toBe(240)
+ })
+
+ it('returns default widths', () => {
+ const { result } = renderHook(() => useLayoutPanels())
+ expect(result.current.sidebarWidth).toBe(250)
+ expect(result.current.noteListWidth).toBe(300)
+ expect(result.current.inspectorWidth).toBe(280)
+ })
+
+ it('clamps sidebar resize to minimum', () => {
+ const { result } = renderHook(() => useLayoutPanels())
+ act(() => result.current.handleSidebarResize(-500))
+ expect(result.current.sidebarWidth).toBe(COLUMN_MIN_WIDTHS.sidebar)
+ })
+
+ it('clamps note list resize to minimum', () => {
+ const { result } = renderHook(() => useLayoutPanels())
+ act(() => result.current.handleNoteListResize(-500))
+ expect(result.current.noteListWidth).toBe(COLUMN_MIN_WIDTHS.noteList)
+ })
+
+ it('clamps inspector resize to minimum', () => {
+ const { result } = renderHook(() => useLayoutPanels())
+ act(() => result.current.handleInspectorResize(500))
+ expect(result.current.inspectorWidth).toBe(COLUMN_MIN_WIDTHS.inspector)
+ })
+
+ it('clamps sidebar resize to maximum', () => {
+ const { result } = renderHook(() => useLayoutPanels())
+ act(() => result.current.handleSidebarResize(500))
+ expect(result.current.sidebarWidth).toBe(400)
+ })
+
+ it('clamps note list resize to maximum', () => {
+ const { result } = renderHook(() => useLayoutPanels())
+ act(() => result.current.handleNoteListResize(500))
+ expect(result.current.noteListWidth).toBe(500)
+ })
+
+ it('clamps inspector resize to maximum', () => {
+ const { result } = renderHook(() => useLayoutPanels())
+ act(() => result.current.handleInspectorResize(-500))
+ expect(result.current.inspectorWidth).toBe(500)
+ })
+})
diff --git a/src/hooks/useLayoutPanels.ts b/src/hooks/useLayoutPanels.ts
index 192c276f..d2b15b25 100644
--- a/src/hooks/useLayoutPanels.ts
+++ b/src/hooks/useLayoutPanels.ts
@@ -1,12 +1,19 @@
import { useCallback, useState } from 'react'
+export const COLUMN_MIN_WIDTHS = {
+ sidebar: 180,
+ noteList: 220,
+ editor: 400,
+ inspector: 240,
+} as const
+
export function useLayoutPanels() {
const [sidebarWidth, setSidebarWidth] = useState(250)
const [noteListWidth, setNoteListWidth] = useState(300)
const [inspectorWidth, setInspectorWidth] = useState(280)
const [inspectorCollapsed, setInspectorCollapsed] = useState(false)
- const handleSidebarResize = useCallback((delta: number) => setSidebarWidth((w) => Math.max(150, Math.min(400, w + delta))), [])
- const handleNoteListResize = useCallback((delta: number) => setNoteListWidth((w) => Math.max(200, Math.min(500, w + delta))), [])
- const handleInspectorResize = useCallback((delta: number) => setInspectorWidth((w) => Math.max(200, Math.min(500, w - delta))), [])
+ const handleSidebarResize = useCallback((delta: number) => setSidebarWidth((w) => Math.max(COLUMN_MIN_WIDTHS.sidebar, Math.min(400, w + delta))), [])
+ const handleNoteListResize = useCallback((delta: number) => setNoteListWidth((w) => Math.max(COLUMN_MIN_WIDTHS.noteList, Math.min(500, w + delta))), [])
+ const handleInspectorResize = useCallback((delta: number) => setInspectorWidth((w) => Math.max(COLUMN_MIN_WIDTHS.inspector, Math.min(500, w - delta))), [])
return { sidebarWidth, noteListWidth, inspectorWidth, inspectorCollapsed, setInspectorCollapsed, handleSidebarResize, handleNoteListResize, handleInspectorResize }
}