From 3ed8872f16eefab2258f6fcf373b99027e2cbf02 Mon Sep 17 00:00:00 2001 From: kumarabhirup Date: Mon, 2 Mar 2026 18:35:34 -0800 Subject: [PATCH] refactor(web): move profile management to workspace-sidebar --- apps/web/app/components/sidebar.tsx | 31 +--- .../workspace/workspace-sidebar.tsx | 167 +++++++++++------- 2 files changed, 111 insertions(+), 87 deletions(-) diff --git a/apps/web/app/components/sidebar.tsx b/apps/web/app/components/sidebar.tsx index f4575c955b4..83c7b15b3b6 100644 --- a/apps/web/app/components/sidebar.tsx +++ b/apps/web/app/components/sidebar.tsx @@ -2,8 +2,6 @@ import { useEffect, useState, useCallback } from "react"; import { FileManagerTree } from "./workspace/file-manager-tree"; -import { ProfileSwitcher } from "./workspace/profile-switcher"; -import { CreateWorkspaceDialog } from "./workspace/create-workspace-dialog"; // --- Types --- @@ -353,9 +351,8 @@ export function Sidebar({ const [mainMemory, setMainMemory] = useState(null); const [dailyLogs, setDailyLogs] = useState([]); const [workspaceTree, setWorkspaceTree] = useState([]); + const [activeProfile, setActiveProfile] = useState("default"); const [loading, setLoading] = useState(true); - const [showCreateWorkspace, setShowCreateWorkspace] = useState(false); - const [sidebarRefreshKey, setSidebarRefreshKey] = useState(0); const toggleSection = (section: SidebarSection) => { setOpenSections((prev) => { @@ -366,27 +363,24 @@ export function Sidebar({ }); }; - // Full sidebar re-fetch after profile switch or workspace creation - const handleProfileSwitch = useCallback(() => { - setSidebarRefreshKey((k) => k + 1); - }, []); - - // Fetch sidebar data (re-runs when refreshKey or sidebarRefreshKey changes) + // Fetch sidebar data useEffect(() => { async function load() { setLoading(true); try { - const [webSessionsRes, skillsRes, memoriesRes, workspaceRes] = await Promise.all([ + const [webSessionsRes, skillsRes, memoriesRes, workspaceRes, profilesRes] = await Promise.all([ fetch("/api/web-sessions").then((r) => r.json()), fetch("/api/skills").then((r) => r.json()), fetch("/api/memories").then((r) => r.json()), fetch("/api/workspace/tree").then((r) => r.json()).catch(() => ({ tree: [] })), + fetch("/api/profiles").then((r) => r.json()).catch(() => ({ activeProfile: "default" })), ]); setWebSessions(webSessionsRes.sessions ?? []); setSkills(skillsRes.skills ?? []); setMainMemory(memoriesRes.mainMemory ?? null); setDailyLogs(memoriesRes.dailyLogs ?? []); setWorkspaceTree(workspaceRes.tree ?? []); + setActiveProfile(String(profilesRes.activeProfile || "default")); } catch (err) { console.error("Failed to load sidebar data:", err); } finally { @@ -394,7 +388,7 @@ export function Sidebar({ } } void load(); - }, [refreshKey, sidebarRefreshKey]); + }, [refreshKey]); const refreshWorkspace = useCallback(async () => { try { @@ -434,20 +428,9 @@ export function Sidebar({ - setShowCreateWorkspace(true)} - activeProfileHint={String(sidebarRefreshKey)} - /> +

Profile: {activeProfile}

- {/* Create workspace dialog */} - setShowCreateWorkspace(false)} - onCreated={handleProfileSwitch} - /> - {/* Content */}
{loading ? ( diff --git a/apps/web/app/components/workspace/workspace-sidebar.tsx b/apps/web/app/components/workspace/workspace-sidebar.tsx index e8db2a1538b..609065499db 100644 --- a/apps/web/app/components/workspace/workspace-sidebar.tsx +++ b/apps/web/app/components/workspace/workspace-sidebar.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useState, useRef, useCallback } from "react"; +import { useEffect, useState, useRef, useCallback, useMemo } from "react"; import { FileManagerTree, type TreeNode } from "./file-manager-tree"; import { ProfileSwitcher } from "./profile-switcher"; import { CreateWorkspaceDialog } from "./create-workspace-dialog"; @@ -40,18 +40,18 @@ type WorkspaceSidebarProps = { mobile?: boolean; /** Close the mobile drawer. */ onClose?: () => void; - /** Active workspace profile name (null = default). */ - activeProfile?: string | null; /** Fixed width in px when not mobile (overrides default 260). */ width?: number; - /** Called after the user switches to a different profile. */ - onProfileSwitch?: () => void; /** Whether hidden (dot) files/folders are currently shown. */ showHidden?: boolean; /** Toggle hidden files visibility. */ onToggleHidden?: () => void; /** Called when the user clicks the collapse/hide sidebar button. */ onCollapse?: () => void; + /** Active profile hint used by the profile switcher. */ + activeProfile?: string | null; + /** Called after profile switches or workspace creation so parent can refresh state. */ + onProfileChanged?: () => void; }; function HomeIcon() { @@ -389,6 +389,27 @@ function dirDisplayName(dir: string): string { return dir.split("/").pop() || dir; } +function filterSidebarTree(nodes: TreeNode[]): TreeNode[] { + const filtered: TreeNode[] = []; + for (const node of nodes) { + // Root identity file is system-managed and hidden in Ironclaw UI. + if (node.path === "IDENTITY.md") { + continue; + } + // Dench is an always-on managed skill; hide it from the sidebar list. + if (node.path === "~skills/dench/SKILL.md") { + continue; + } + + const children = node.children ? filterSidebarTree(node.children) : undefined; + if (node.path === "~skills" && (!children || children.length === 0)) { + continue; + } + filtered.push(children ? { ...node, children } : node); + } + return filtered; +} + export function WorkspaceSidebar({ tree, activePath, @@ -406,16 +427,17 @@ export function WorkspaceSidebar({ onExternalDrop, mobile, onClose, - activeProfile, - onProfileSwitch, showHidden, onToggleHidden, width: widthProp, onCollapse, + activeProfile, + onProfileChanged, }: WorkspaceSidebarProps) { const isBrowsing = browseDir != null; - const [showCreateWorkspace, setShowCreateWorkspace] = useState(false); const width = mobile ? "280px" : (widthProp ?? 260); + const visibleTree = useMemo(() => filterSidebarTree(tree), [tree]); + const [createWorkspaceOpen, setCreateWorkspaceOpen] = useState(false); const sidebar = (
-
- Ironclaw - {profileName && profileName !== "default" && ( - - {profileName} - - )} -
- - - - - - )} - /> + Profile + + {profileName || "default"} + + + + + + )} + /> + + )} {onCollapse && ( @@ -551,13 +577,6 @@ export function WorkspaceSidebar({ )} - {/* Create workspace dialog */} - setShowCreateWorkspace(false)} - onCreated={onProfileSwitch} - /> - {/* File search */} {onFileSearchSelect && ( @@ -575,7 +594,7 @@ export function WorkspaceSidebar({ ) : ( ); - if (!mobile) { return sidebar; } + if (!mobile) { + return ( + <> + {sidebar} + setCreateWorkspaceOpen(false)} + onCreated={() => { + onProfileChanged?.(); + }} + /> + + ); + } return ( -
void onClose?.()}> - {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */} -
e.stopPropagation()} className="fixed inset-y-0 left-0 z-50"> - {sidebar} + <> +
void onClose?.()}> + {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */} +
e.stopPropagation()} className="fixed inset-y-0 left-0 z-50"> + {sidebar} +
-
+ setCreateWorkspaceOpen(false)} + onCreated={() => { + onProfileChanged?.(); + }} + /> + ); }