1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
import React, { useState, useEffect } from "react";
import type { Session, Phase } from "../types";
const api = window.api;
type Theme = "dark" | "light";
interface HeaderProps {
selectedSession: Session | null;
theme: Theme;
onToggleTheme: () => void;
gitBranch: string | null;
onOpenSettings: () => void;
viewPhase: Phase;
onViewPhase: (phase: Phase) => void;
}
const phaseLabels: Record<Phase, string> = {
research: "Research",
plan: "Plan",
implement: "Implement",
};
const phases: Phase[] = ["research", "plan", "implement"];
export function Header({
selectedSession,
theme,
onToggleTheme,
gitBranch,
onOpenSettings,
viewPhase,
onViewPhase,
}: HeaderProps) {
// ── Maximize ─────────────────────────────────────────────────
const [isMaximized, setIsMaximized] = useState(false);
useEffect(() => {
return api.onWindowMaximized(setIsMaximized);
}, []);
// ── Branch copy ──────────────────────────────────────────────
const [copied, setCopied] = useState(false);
const handleCopyBranch = () => {
if (!gitBranch) return;
navigator.clipboard.writeText(gitBranch);
setCopied(true);
setTimeout(() => setCopied(false), 1500);
};
return (
<header className="header">
<div className="header-left">
<span className="app-wordmark">Claude Flow</span>
</div>
<div className="header-right">
{selectedSession && (
<div className="phase-indicator">
{phases.map((phase) => {
const phaseIndex = phases.indexOf(phase);
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 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>
);
})}
</div>
)}
{/* ── Branch badge ── */}
{selectedSession && gitBranch && (
<button
className={["branch-badge", copied ? "branch-copied" : ""]
.filter(Boolean)
.join(" ")}
onClick={handleCopyBranch}
title={copied ? "Copied!" : `Click to copy: ${gitBranch}`}
>
⎇ {gitBranch}
</button>
)}
{/* ── Theme toggle ── */}
<button className="theme-toggle" onClick={onToggleTheme}>
{theme === "dark" ? "[light]" : "[dark]"}
</button>
{/* ── Maximize toggle ── */}
<button
className="maximize-btn"
onClick={() => api.toggleMaximize()}
title={isMaximized ? "Restore window" : "Maximize window"}
>
{isMaximized ? "⊡" : "□"}
</button>
{/* ── Settings button ── */}
<button className="settings-btn" onClick={onOpenSettings} title="Settings">
⚙
</button>
</div>
</header>
);
}
|