Tabs: '+' opens brov-chat or term.macOS (max 6, split around the camera); real zsh terminals via SwiftTerm; per-tab chat history and claude session; hover 3 s to close with confirmation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,26 @@ final class AppState: ObservableObject {
|
||||
|
||||
// Island state
|
||||
@Published var mode: IslandMode = .hidden
|
||||
@Published var view: IslandView = .overview
|
||||
@Published var view: IslandView = .overview {
|
||||
didSet {
|
||||
// Any way into the chat (drop → "Задать вопрос", shortcuts…) lands in a brov-chat tab.
|
||||
if view == .prompt, tabs.first(where: { $0.id == activeTabId })?.kind != .chat {
|
||||
if let chat = tabs.first(where: { $0.kind == .chat }) {
|
||||
if let cur = activeTabId, tabs.first(where: { $0.id == cur })?.kind == .chat {
|
||||
ChatTabs.shared.saveHistory(chatHistory, for: cur)
|
||||
}
|
||||
activeTabId = chat.id
|
||||
chatHistory = ChatTabs.shared.history(for: chat.id)
|
||||
} else if tabs.count < Self.maxTabs {
|
||||
let n = (1...Self.maxTabs).first { k in !tabs.contains { $0.kind == .chat && $0.number == k } } ?? 1
|
||||
let t = BroTab(kind: .chat, number: n)
|
||||
tabs.append(t)
|
||||
activeTabId = t.id
|
||||
chatHistory = []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tasks
|
||||
@Published var tasks: [AgentTask] = []
|
||||
@@ -420,7 +439,7 @@ final class AppState: ObservableObject {
|
||||
}
|
||||
|
||||
func chatDetentHeight(_ d: Int) -> CGFloat {
|
||||
let auto = min(300, 240 + CGFloat(chatHistory.count) * 40)
|
||||
let auto = view == .terminal ? 380 : min(300, 240 + CGFloat(chatHistory.count) * 40)
|
||||
switch d {
|
||||
case 1: return max(320, screenHeight / 3)
|
||||
case 2: return max(340, screenHeight / 2)
|
||||
@@ -432,6 +451,48 @@ final class AppState: ObservableObject {
|
||||
/// Current chat island height (drag, else detent).
|
||||
var chatIslandHeight: CGFloat { chatDragHeight ?? chatDetentHeight(chatDetent) }
|
||||
|
||||
// MARK: Tabs (BroV): brov-chat dialogs and term.macOS terminals
|
||||
static let maxTabs = 6
|
||||
@Published var tabs: [BroTab] = [BroTab(kind: .chat, number: 1)]
|
||||
@Published var activeTabId: UUID? = nil
|
||||
|
||||
func openTab(_ kind: TabKind) {
|
||||
guard tabs.count < Self.maxTabs else { return }
|
||||
let used = Set(tabs.filter { $0.kind == kind }.map(\.number))
|
||||
let n = (1...Self.maxTabs).first { !used.contains($0) } ?? tabs.count + 1
|
||||
let tab = BroTab(kind: kind, number: n)
|
||||
tabs.append(tab)
|
||||
SoundEngine.shared.play("pop")
|
||||
activateTab(tab.id)
|
||||
}
|
||||
|
||||
func activateTab(_ id: UUID) {
|
||||
guard let tab = tabs.first(where: { $0.id == id }) else { return }
|
||||
if let cur = activeTabId, cur != id, tabs.first(where: { $0.id == cur })?.kind == .chat {
|
||||
ChatTabs.shared.saveHistory(chatHistory, for: cur)
|
||||
}
|
||||
if activeTabId != id {
|
||||
activeTabId = id
|
||||
if tab.kind == .chat { chatHistory = ChatTabs.shared.history(for: id) }
|
||||
}
|
||||
view = tab.kind == .chat ? .prompt : .terminal
|
||||
}
|
||||
|
||||
func closeTab(_ id: UUID) {
|
||||
guard let i = tabs.firstIndex(where: { $0.id == id }) else { return }
|
||||
let tab = tabs.remove(at: i)
|
||||
if tab.kind == .chat { ChatTabs.shared.close(id) } else { TerminalStore.shared.close(id) }
|
||||
SoundEngine.shared.play("close")
|
||||
guard activeTabId == id else { return }
|
||||
activeTabId = nil
|
||||
if tab.kind == .chat { chatHistory = [] }
|
||||
if let next = tabs.indices.contains(i) ? tabs[i] : tabs.last {
|
||||
activateTab(next.id)
|
||||
} else {
|
||||
view = .overview
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Agent health (BroV)
|
||||
@Published var crashedSessions: [CrashedSession] = []
|
||||
@Published var claudeNeedsLogin = false
|
||||
@@ -466,6 +527,7 @@ final class AppState: ObservableObject {
|
||||
// MARK: - Init (loads persisted settings)
|
||||
|
||||
private init() {
|
||||
activeTabId = tabs.first?.id
|
||||
let ud = UserDefaults.standard
|
||||
|
||||
if let v = ud.object(forKey: "soundEnabled") as? Bool { soundEnabled = v }
|
||||
|
||||
Reference in New Issue
Block a user