From b6405dd6a4ba65fc5dc6746db7be7be7d0bb29f3 Mon Sep 17 00:00:00 2001
From: bndw <ben@bdw.to>
Date: Wed, 4 Mar 2026 21:21:22 -0800
Subject: feat: replace header dropdowns with collapsible sidebar tree

- Add Sidebar.tsx: project/session tree with inline rename, collapse/resize
- App.tsx: load all sessions at startup, sync selectedProject on session click
- Header.tsx: strip project/session UI, keep only right-side controls
- globals.css: add .main-layout, sidebar, item, and activity-dot styles
- Chat pane: move toggle button to left, use triangle icons matching sidebar
---
 renderer/src/components/Header.tsx | 172 +++++--------------------------------
 1 file changed, 21 insertions(+), 151 deletions(-)

(limited to 'renderer/src/components/Header.tsx')

diff --git a/renderer/src/components/Header.tsx b/renderer/src/components/Header.tsx
index dc88a73..4e193e8 100644
--- a/renderer/src/components/Header.tsx
+++ b/renderer/src/components/Header.tsx
@@ -1,27 +1,18 @@
 import React, { useState, useEffect } from "react";
-import type { Project, Session, Phase } from "../types";
-import { formatSessionLabel } from "../utils/timeFormat";
+import type { Session, Phase } from "../types";
 
 const api = window.api;
 
 type Theme = "dark" | "light";
 
 interface HeaderProps {
-  projects: Project[];
-  sessions: Session[];
-  selectedProject: Project | null;
   selectedSession: Session | null;
-  onSelectProject: (project: Project | null) => void;
-  onSelectSession: (session: Session | null) => void;
-  onCreateProject: () => void;
-  onCreateSession: () => void;
-  onDeleteProject?: (id: string) => void;
-  onDeleteSession?: (id: string) => void;
-  onRenameSession?: (id: string, name: string) => void;
   theme: Theme;
   onToggleTheme: () => void;
   gitBranch: string | null;
   onOpenSettings: () => void;
+  viewPhase: Phase;
+  onViewPhase: (phase: Phase) => void;
 }
 
 const phaseLabels: Record<Phase, string> = {
@@ -33,69 +24,18 @@ const phaseLabels: Record<Phase, string> = {
 const phases: Phase[] = ["research", "plan", "implement"];
 
 export function Header({
-  projects,
-  sessions,
-  selectedProject,
   selectedSession,
-  onSelectProject,
-  onSelectSession,
-  onCreateProject,
-  onCreateSession,
-  onDeleteProject,
-  onDeleteSession,
-  onRenameSession,
   theme,
   onToggleTheme,
   gitBranch,
   onOpenSettings,
+  viewPhase,
+  onViewPhase,
 }: HeaderProps) {
-  const handleDeleteProject = () => {
-    if (!selectedProject || !onDeleteProject) return;
-    if (confirm(`Delete project "${selectedProject.name}"? This cannot be undone.`)) {
-      onDeleteProject(selectedProject.id);
-    }
-  };
-
-  const handleDeleteSession = () => {
-    if (!selectedSession || !onDeleteSession) return;
-    if (confirm(`Delete session "${selectedSession.name}"? This cannot be undone.`)) {
-      onDeleteSession(selectedSession.id);
-    }
-  };
-
-  const [isRenamingSession, setIsRenamingSession] = useState(false);
-  const [renameValue, setRenameValue] = useState("");
-  // Guard against double-commit (onKeyDown Enter → unmount → onBlur)
-  const renameCommitted = React.useRef(false);
-
-  const startRename = () => {
-    if (!selectedSession) return;
-    renameCommitted.current = false;
-    setRenameValue(selectedSession.name);
-    setIsRenamingSession(true);
-  };
-
-  const commitRename = () => {
-    if (renameCommitted.current) return;
-    renameCommitted.current = true;
-    if (selectedSession && onRenameSession && renameValue.trim()) {
-      onRenameSession(selectedSession.id, renameValue.trim());
-    }
-    setIsRenamingSession(false);
-  };
-
-  const cancelRename = () => {
-    renameCommitted.current = true; // prevent blur from committing after cancel
-    setIsRenamingSession(false);
-  };
-
   // ── Maximize ─────────────────────────────────────────────────
   const [isMaximized, setIsMaximized] = useState(false);
 
   useEffect(() => {
-    // Returns the unsubscribe function; React cleanup calls it on unmount.
-    // On macOS, clicking the native green traffic light also fires this,
-    // keeping the glyph accurate when native controls are used.
     return api.onWindowMaximized(setIsMaximized);
   }, []);
 
@@ -112,89 +52,7 @@ export function Header({
   return (
     <header className="header">
       <div className="header-left">
-        {/* ── Wordmark ── */}
         <span className="app-wordmark">Claude Flow</span>
-
-        <select
-          value={selectedProject?.id || ""}
-          onChange={(e) => {
-            const project = projects.find((p) => p.id === e.target.value);
-            onSelectProject(project || null);
-            onSelectSession(null);
-          }}
-        >
-          <option value="">Select Project...</option>
-          {projects.map((p) => (
-            <option key={p.id} value={p.id}>
-              {p.name}
-            </option>
-          ))}
-        </select>
-        <button onClick={onCreateProject}>+ Project</button>
-        {selectedProject && onDeleteProject && (
-          <button
-            onClick={handleDeleteProject}
-            className="btn-delete"
-            title="Delete project"
-          >
-            🗑️
-          </button>
-        )}
-
-        {selectedProject && (
-          <>
-            {isRenamingSession ? (
-              <input
-                autoFocus
-                value={renameValue}
-                onChange={(e) => setRenameValue(e.target.value)}
-                onKeyDown={(e) => {
-                  if (e.key === "Enter") commitRename();
-                  if (e.key === "Escape") cancelRename();
-                }}
-                onBlur={commitRename}
-                className="session-rename-input"
-              />
-            ) : (
-              <select
-                value={selectedSession?.id || ""}
-                onChange={(e) => {
-                  const session = sessions.find((s) => s.id === e.target.value);
-                  onSelectSession(session || null);
-                }}
-              >
-                <option value="">Select Session...</option>
-                {sessions.map((s) => (
-                  <option key={s.id} value={s.id}>
-                    {formatSessionLabel(s.name, s.updated_at)}
-                  </option>
-                ))}
-              </select>
-            )}
-            <button onClick={onCreateSession}>+ Session</button>
-            {selectedSession &&
-              onRenameSession &&
-              !isRenamingSession &&
-              selectedSession.phase !== "implement" && (
-                <button
-                  onClick={startRename}
-                  className="btn-rename"
-                  title="Rename session"
-                >
-                  ✏️
-                </button>
-              )}
-            {selectedSession && onDeleteSession && (
-              <button
-                onClick={handleDeleteSession}
-                className="btn-delete"
-                title="Delete session"
-              >
-                🗑️
-              </button>
-            )}
-          </>
-        )}
       </div>
 
       <div className="header-right">
@@ -205,15 +63,27 @@ export function Header({
               const currentIndex = phases.indexOf(selectedSession.phase);
               const isComplete = phaseIndex < currentIndex;
               const isActive = phase === selectedSession.phase;
+              const isReachable = phaseIndex <= currentIndex;
+              const isViewing = phase === viewPhase && !isActive;
 
-              return (
-                <span
+              return isReachable ? (
+                <button
                   key={phase}
                   className={`phase-step ${isActive ? "active" : ""} ${
                     isComplete ? "complete" : ""
-                  }`}
+                  } ${isViewing ? "viewing" : ""}`}
+                  onClick={() => onViewPhase(phase)}
+                  title={
+                    isActive
+                      ? `Viewing ${phaseLabels[phase]} (current)`
+                      : `View ${phaseLabels[phase]} artifact`
+                  }
                 >
                   {phaseLabels[phase]}
+                </button>
+              ) : (
+                <span key={phase} className="phase-step">
+                  {phaseLabels[phase]}
                 </span>
               );
             })}
@@ -244,7 +114,7 @@ export function Header({
           onClick={() => api.toggleMaximize()}
           title={isMaximized ? "Restore window" : "Maximize window"}
         >
-          {isMaximized ? '⊡' : '□'}
+          {isMaximized ? "⊡" : "□"}
         </button>
 
         {/* ── Settings button ── */}
-- 
cgit v1.2.3

