Skip to content

Commit 2000d08

Browse files
grokifyclaude
andcommitted
feat(desktop): add new session sheet and settings view
NewSessionSheet for creating tmux sessions with agent type selection. SettingsView placeholder for future configuration options. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 6e65bae commit 2000d08

File tree

2 files changed

+231
-0
lines changed

2 files changed

+231
-0
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import SwiftUI
2+
3+
/// Sheet for creating a new tmux session
4+
struct NewSessionSheet: View {
5+
@Environment(\.dismiss) private var dismiss
6+
7+
@State private var sessionName: String = ""
8+
@State private var selectedAgentType: AgentType?
9+
@State private var customCommand: String = ""
10+
@State private var useCustomCommand: Bool = false
11+
@State private var isCreating: Bool = false
12+
@State private var errorMessage: String?
13+
14+
let sessionManager: SessionManager
15+
let onSessionCreated: (NexusSession) -> Void
16+
17+
var body: some View {
18+
VStack(spacing: 0) {
19+
// Header
20+
HStack {
21+
Text("New Session")
22+
.font(.headline)
23+
Spacer()
24+
Button("Cancel") {
25+
dismiss()
26+
}
27+
.keyboardShortcut(.cancelAction)
28+
}
29+
.padding()
30+
31+
Divider()
32+
33+
// Form
34+
Form {
35+
// Session name
36+
TextField("Session Name", text: $sessionName)
37+
.textFieldStyle(.roundedBorder)
38+
39+
// Agent type (optional)
40+
Picker("Agent Type", selection: $selectedAgentType) {
41+
Text("None").tag(nil as AgentType?)
42+
ForEach(AgentType.allCases, id: \.self) { type in
43+
Text(type.displayName).tag(type as AgentType?)
44+
}
45+
}
46+
47+
// Custom command toggle
48+
Toggle("Use custom command", isOn: $useCustomCommand)
49+
50+
if useCustomCommand {
51+
TextField("Command", text: $customCommand)
52+
.textFieldStyle(.roundedBorder)
53+
.help("Command to run in the session (default: your shell)")
54+
}
55+
56+
// Error message
57+
if let error = errorMessage {
58+
Text(error)
59+
.foregroundColor(.red)
60+
.font(.caption)
61+
}
62+
}
63+
.padding()
64+
65+
Divider()
66+
67+
// Footer
68+
HStack {
69+
Spacer()
70+
Button("Create") {
71+
createSession()
72+
}
73+
.buttonStyle(.borderedProminent)
74+
.keyboardShortcut(.defaultAction)
75+
.disabled(sessionName.isEmpty || isCreating)
76+
}
77+
.padding()
78+
}
79+
.frame(width: 400)
80+
.onAppear {
81+
// Generate a default name
82+
sessionName = generateDefaultName()
83+
}
84+
}
85+
86+
private func generateDefaultName() -> String {
87+
let existingNames = Set(sessionManager.sessions.map { $0.name })
88+
var index = 1
89+
var name = "session-\(index)"
90+
91+
while existingNames.contains(name) {
92+
index += 1
93+
name = "session-\(index)"
94+
}
95+
96+
return name
97+
}
98+
99+
private func createSession() {
100+
guard !sessionName.isEmpty else { return }
101+
102+
isCreating = true
103+
errorMessage = nil
104+
105+
let command = useCustomCommand && !customCommand.isEmpty ? customCommand : nil
106+
107+
Task {
108+
do {
109+
var session = try await sessionManager.createSession(name: sessionName, command: command)
110+
session.agentType = selectedAgentType
111+
112+
await MainActor.run {
113+
onSessionCreated(session)
114+
dismiss()
115+
}
116+
} catch {
117+
await MainActor.run {
118+
errorMessage = error.localizedDescription
119+
isCreating = false
120+
}
121+
}
122+
}
123+
}
124+
}
125+
126+
#Preview {
127+
NewSessionSheet(
128+
sessionManager: SessionManager(),
129+
onSessionCreated: { _ in }
130+
)
131+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import SwiftUI
2+
3+
/// Settings/Preferences window
4+
struct SettingsView: View {
5+
var body: some View {
6+
TabView {
7+
GeneralSettingsView()
8+
.tabItem {
9+
Label("General", systemImage: "gear")
10+
}
11+
12+
AppearanceSettingsView()
13+
.tabItem {
14+
Label("Appearance", systemImage: "paintbrush")
15+
}
16+
17+
SessionSettingsView()
18+
.tabItem {
19+
Label("Sessions", systemImage: "terminal")
20+
}
21+
}
22+
.frame(width: 450, height: 300)
23+
}
24+
}
25+
26+
struct GeneralSettingsView: View {
27+
@AppStorage("autoAttachOnLaunch") private var autoAttachOnLaunch = true
28+
@AppStorage("showDetachedSessions") private var showDetachedSessions = true
29+
@AppStorage("restoreWindowsOnLaunch") private var restoreWindowsOnLaunch = true
30+
31+
var body: some View {
32+
Form {
33+
Toggle("Auto-attach to last session on launch", isOn: $autoAttachOnLaunch)
34+
Toggle("Show detached sessions in status bar", isOn: $showDetachedSessions)
35+
Toggle("Restore windows on launch", isOn: $restoreWindowsOnLaunch)
36+
}
37+
.padding()
38+
}
39+
}
40+
41+
struct AppearanceSettingsView: View {
42+
@AppStorage("terminalFontSize") private var fontSize: Double = 13
43+
@AppStorage("cursorBlink") private var cursorBlink = true
44+
45+
var body: some View {
46+
Form {
47+
HStack {
48+
Text("Font Size:")
49+
Slider(value: $fontSize, in: 9...24, step: 1) {
50+
Text("Font Size")
51+
}
52+
Text("\(Int(fontSize))pt")
53+
.monospacedDigit()
54+
.frame(width: 40)
55+
}
56+
57+
Toggle("Cursor blink", isOn: $cursorBlink)
58+
59+
// Placeholder for theme selection
60+
Text("Theme settings coming in a future update")
61+
.foregroundColor(.secondary)
62+
.font(.caption)
63+
}
64+
.padding()
65+
}
66+
}
67+
68+
struct SessionSettingsView: View {
69+
@AppStorage("idleThresholdSeconds") private var idleThreshold: Double = 30
70+
@AppStorage("stuckThresholdSeconds") private var stuckThreshold: Double = 120
71+
@AppStorage("defaultShell") private var defaultShell = "/bin/zsh"
72+
73+
var body: some View {
74+
Form {
75+
HStack {
76+
Text("Idle after:")
77+
Slider(value: $idleThreshold, in: 10...120, step: 10)
78+
Text("\(Int(idleThreshold))s")
79+
.monospacedDigit()
80+
.frame(width: 40)
81+
}
82+
83+
HStack {
84+
Text("Stuck after:")
85+
Slider(value: $stuckThreshold, in: 60...600, step: 30)
86+
Text("\(Int(stuckThreshold))s")
87+
.monospacedDigit()
88+
.frame(width: 40)
89+
}
90+
91+
TextField("Default Shell:", text: $defaultShell)
92+
.textFieldStyle(.roundedBorder)
93+
}
94+
.padding()
95+
}
96+
}
97+
98+
#Preview {
99+
SettingsView()
100+
}

0 commit comments

Comments
 (0)