From 4db7e9690c090034f2ed7fb4f4b4404dc475e184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20G=C3=B3mez?= Date: Thu, 18 Dec 2025 12:24:00 +0100 Subject: [PATCH] refactor(layouts): simplify to global config + preserve windows + add UI dropdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simplified layout system based on user feedback: **1. Global Layout Config (Simpler)** - Moved layoutConfig from per-workspace to global state - One configuration applies to all workspaces (easier to understand) - Updated state migration v8→v9 to move config to global level - Updated WorkspaceSettings UI to edit global config - Renamed updateWorkspaceLayoutConfig → updateLayoutConfig **2. Preserve Extra Windows (Fixed Bug)** - Fixed applyPresetToLayout to keep windows beyond preset slots - When applying 4-window grid to 6 windows, windows 5-6 are preserved - Extra windows stacked vertically on right side (70/30 split) - No more window loss when applying presets **3. Layout Dropdown in TabBar (Better UX)** - Added dropdown menu next to workspace tabs - Shows all available presets with icons (Grid2X2, Columns2, Split) - Displays window requirements and availability - Disables presets that need more windows than available - One-click preset application with toast feedback - More accessible than /layout command All tests passing (457 passed). State migration handles v6→v7→v8→v9 correctly. Generated with [Claude Code](https://claude.com/claude-code) --- src/components/TabBar.tsx | 119 +++++++++++++++++++++---- src/components/WorkspaceSettings.tsx | 22 ++--- src/core/logic.test.ts | 4 +- src/core/logic.ts | 30 ++----- src/core/state.ts | 25 +++--- src/lib/layout-presets.ts | 34 ++++++- src/lib/migrations.test.ts | 127 +++++++++++++++++++-------- src/lib/migrations.ts | 29 +++++- src/types/app.ts | 2 +- 9 files changed, 277 insertions(+), 115 deletions(-) diff --git a/src/components/TabBar.tsx b/src/components/TabBar.tsx index 54d0ab8..26e0d99 100644 --- a/src/components/TabBar.tsx +++ b/src/components/TabBar.tsx @@ -1,24 +1,71 @@ -import { Plus, SlidersHorizontal } from "lucide-react"; +import { Plus, SlidersHorizontal, Grid2X2, Columns2, Split } from "lucide-react"; import { Button } from "./ui/button"; import { useGrimoire } from "@/core/state"; import { cn } from "@/lib/utils"; import { WorkspaceSettings } from "./WorkspaceSettings"; +import { getAllPresets } from "@/lib/layout-presets"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "./ui/dropdown-menu"; +import { toast } from "sonner"; import { useState } from "react"; export function TabBar() { - const { state, setActiveWorkspace, createWorkspace } = useGrimoire(); + const { state, setActiveWorkspace, createWorkspace, applyPresetLayout } = useGrimoire(); const { workspaces, activeWorkspaceId } = state; - const [settingsWorkspaceId, setSettingsWorkspaceId] = useState( - null, - ); + const [settingsOpen, setSettingsOpen] = useState(false); + + const activeWorkspace = workspaces[activeWorkspaceId]; + const windowCount = activeWorkspace?.windowIds.length || 0; + const presets = getAllPresets(); const handleNewTab = () => { createWorkspace(); }; - const handleSettingsClick = (e: React.MouseEvent, workspaceId: string) => { + const handleSettingsClick = (e: React.MouseEvent) => { e.stopPropagation(); // Prevent workspace switch - setSettingsWorkspaceId(workspaceId); + setSettingsOpen(true); + }; + + const handleApplyPreset = (presetId: string) => { + const preset = presets.find((p) => p.id === presetId); + if (!preset) return; + + if (windowCount < preset.slots) { + toast.error(`Not enough windows`, { + description: `Preset "${preset.name}" requires ${preset.slots} windows, but only ${windowCount} available.`, + }); + return; + } + + try { + applyPresetLayout(preset); + toast.success(`Layout applied`, { + description: `Applied "${preset.name}" preset to workspace ${activeWorkspace.number}`, + }); + } catch (error) { + toast.error(`Failed to apply layout`, { + description: + error instanceof Error ? error.message : "Unknown error occurred", + }); + } + }; + + const getPresetIcon = (presetId: string) => { + switch (presetId) { + case "side-by-side": + return ; + case "main-sidebar": + return ; + case "grid": + return ; + default: + return ; + } }; // Sort workspaces by number @@ -46,19 +93,60 @@ export function TabBar() { : ws.number} ))} + + {/* Layout Preset Dropdown */} + + + + + +
+ Apply Layout Preset +
+ {presets.map((preset) => { + const canApply = windowCount >= preset.slots; + return ( + handleApplyPreset(preset.id)} + disabled={!canApply} + className="flex items-center gap-3 cursor-pointer" + > +
{getPresetIcon(preset.id)}
+
+
{preset.name}
+
+ {canApply + ? `${preset.slots} windows` + : `Needs ${preset.slots} (have ${windowCount})`} +
+
+
+ ); + })} +
+
+