From 1836deee6c142d1f3e2b27d614bc12dbc0814adf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20G=C3=B3mez?= Date: Sat, 20 Dec 2025 18:52:07 +0100 Subject: [PATCH] feat: add wallet status indicator and replace layout on apply - Add WalletStatus component to TabBar - Change loadSpellbook to replace current workspaces instead of merging - Update SpellbooksViewer toast message --- src/components/SpellbooksViewer.tsx | 2 +- src/components/TabBar.tsx | 2 ++ src/components/WalletStatus.tsx | 48 +++++++++++++++++++++++++ src/lib/spellbook-manager.ts | 55 +++++++++-------------------- 4 files changed, 67 insertions(+), 40 deletions(-) create mode 100644 src/components/WalletStatus.tsx diff --git a/src/components/SpellbooksViewer.tsx b/src/components/SpellbooksViewer.tsx index ac650cc..aa2ce71 100644 --- a/src/components/SpellbooksViewer.tsx +++ b/src/components/SpellbooksViewer.tsx @@ -312,7 +312,7 @@ export function SpellbooksViewer() { const handleApply = (spellbook: ParsedSpellbook) => { loadSpellbook(spellbook); toast.success("Layout applied", { - description: `Added ${Object.keys(spellbook.content.workspaces).length} workspaces.`, + description: `Replaced current layout with ${Object.keys(spellbook.content.workspaces).length} workspaces.`, }); }; diff --git a/src/components/TabBar.tsx b/src/components/TabBar.tsx index 2b204d5..2a7408d 100644 --- a/src/components/TabBar.tsx +++ b/src/components/TabBar.tsx @@ -3,6 +3,7 @@ import { Button } from "./ui/button"; import { useGrimoire } from "@/core/state"; import { cn } from "@/lib/utils"; import { LayoutControls } from "./LayoutControls"; +import { WalletStatus } from "./WalletStatus"; import { useEffect, useState } from "react"; import { Reorder, useDragControls } from "framer-motion"; import { Workspace } from "@/types/app"; @@ -234,6 +235,7 @@ export function TabBar() { {/* Right side: Layout controls */}
+
diff --git a/src/components/WalletStatus.tsx b/src/components/WalletStatus.tsx new file mode 100644 index 0000000..1f772b7 --- /dev/null +++ b/src/components/WalletStatus.tsx @@ -0,0 +1,48 @@ +import { useEffect, useState } from "react"; +import { Wallet } from "lucide-react"; +import walletService from "@/services/wallet"; +import { Button } from "./ui/button"; +import { useGrimoire } from "@/core/state"; +import { cn } from "@/lib/utils"; +import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; + +export function WalletStatus() { + const [status, setStatus] = useState(walletService.status$.value); + const { addWindow } = useGrimoire(); + + useEffect(() => { + const sub = walletService.status$.subscribe(setStatus); + return () => sub.unsubscribe(); + }, []); + + const handleClick = () => { + addWindow("wallet", {}); + }; + + return ( + + + + + +

+ Wallet: {status.charAt(0).toUpperCase() + status.slice(1)} +

+
+
+ ); +} diff --git a/src/lib/spellbook-manager.ts b/src/lib/spellbook-manager.ts index f85f568..da9a8ea 100644 --- a/src/lib/spellbook-manager.ts +++ b/src/lib/spellbook-manager.ts @@ -211,11 +211,9 @@ export function loadSpellbook( const workspaceIdMap = new Map(); const windowIdMap = new Map(); - // 1. Generate new Workspace IDs and Numbers - // We don't want to mess up existing workspace numbers, so we append. - // We'll calculate starting number based on existing. - const newWorkspaces: Record = { ...state.workspaces }; - const newWindows: Record = { ...state.windows }; + // 1. Start fresh + const newWorkspaces: Record = {}; + const newWindows: Record = {}; // 2. Process Windows first to build ID map Object.values(windows).forEach((window) => { @@ -230,8 +228,17 @@ export function loadSpellbook( }); // 3. Process Workspaces - Object.values(workspaces).forEach((ws) => { + // Sort by original number to preserve order + const sortedWorkspaces = Object.values(workspaces).sort( + (a, b) => a.number - b.number + ); + + let firstNewWorkspaceId: string | null = null; + + sortedWorkspaces.forEach((ws) => { const newWsId = uuidv4(); + if (!firstNewWorkspaceId) firstNewWorkspaceId = newWsId; + workspaceIdMap.set(ws.id, newWsId); // Update window IDs in the windowIds array @@ -242,34 +249,8 @@ export function loadSpellbook( // Update layout tree with new window IDs const newLayout = updateLayoutIds(ws.layout, windowIdMap); - // Create new workspace instance - // Note: We use the lowest available number strategy to avoid conflicts - // but this might separate workspaces that were sequential in the spellbook - // if there are gaps in the current state. - // Ideally, we keep them sequential relative to each other. - - // Actually, let's find the max existing number and append after that to keep them grouped - // logic.ts findLowestAvailable fills gaps. - // If we want to group them, we should find the max and start there. - // But findLowestAvailable is the standard convention in this codebase. - // Let's stick to findLowestAvailable for now, but call it sequentially. - - // Wait, findLowestAvailable scans the *current* map. - // So if we add one to newWorkspaces, the next call will find the next number. - - // Simple approach: just use the logic helper against the ACCUMULATING state. - - // However, findLowestAvailable is O(N). Calling it in a loop is O(N^2). - // For small N (workspaces) it's fine. - - const targetNumber = (() => { - // We can't easily use the helper because we haven't added the new workspace yet. - // And if we add it, we need the number first. Catch-22. - // Actually, we can just pass the current state of newWorkspaces to the helper - // provided it matches the signature Record. - // It does. - return findLowestAvailableWorkspaceNumber(newWorkspaces); - })(); + // Assign sequential numbers starting from 1 + const targetNumber = findLowestAvailableWorkspaceNumber(newWorkspaces); newWorkspaces[newWsId] = { ...ws, @@ -277,7 +258,6 @@ export function loadSpellbook( number: targetNumber, layout: newLayout, windowIds: newWindowIds, - // If label exists, keep it, maybe prepend "Imported"? No, keep it as is. }; }); @@ -285,9 +265,6 @@ export function loadSpellbook( ...state, workspaces: newWorkspaces, windows: newWindows, - // Optionally switch to the first imported workspace? - // Let's just return the state, let the UI decide navigation. - // But usually importing implies you want to see it. - // We'll leave activeWorkspaceId unchanged for now, the caller can update it if needed. + activeWorkspaceId: firstNewWorkspaceId || state.activeWorkspaceId, }; }