Compare commits

...

1 Commits

Author SHA1 Message Date
jay b824355cfd feat: T-2.8 StatusBar component + pi state display 2026-05-16 04:17:34 +02:00
2 changed files with 127 additions and 16 deletions

View File

@ -0,0 +1,99 @@
// StatusBar.swift
// T-2.8 Session status bar with pi-state indicator and action buttons.
import Combine
import SwiftUI
@MainActor
struct StatusBar: View {
let sessionName: String
let connectionStatus: String // "Connecting", "Connected", "Disconnected"
@Binding var piState: PiState?
var onSwitcher: (() -> Void)? = nil
var onUnpair: (() -> Void)? = nil
var onSettings: (() -> Void)? = nil
var body: some View {
VStack(spacing: 0) {
HStack {
// Left: session name
Text(sessionName.isEmpty ? " " : sessionName)
.font(.caption.monospaced())
.foregroundStyle(.secondary)
.lineLimit(1)
.truncationMode(.middle)
Spacer()
// Center: pi state / connection status
stateIndicator
Spacer()
// Right: icon buttons
HStack(spacing: 14) {
if onSwitcher != nil {
Button {
onSwitcher?()
} label: {
Image(systemName: "list.bullet")
.font(.caption)
}
}
if onSettings != nil {
Button {
onSettings?()
} label: {
Image(systemName: "gear")
.font(.caption)
}
}
if onUnpair != nil {
Button {
onUnpair?()
} label: {
Image(systemName: "x.circle")
.font(.caption)
.foregroundStyle(.red)
}
}
}
}
.padding(.horizontal, 12)
.padding(.vertical, 6)
.background(Color(uiColor: .systemBackground))
Divider()
}
}
// MARK: - State indicator
@ViewBuilder
private var stateIndicator: some View {
if let state = piState {
switch state {
case .thinking:
Text("● thinking")
.font(.caption.monospaced())
.foregroundStyle(.orange)
case .tool:
Text("▶ tool")
.font(.caption.monospaced())
.foregroundStyle(.blue)
case .awaitingInput:
Text("⏸ awaiting")
.font(.caption.monospaced())
.foregroundStyle(.yellow)
case .idle:
EmptyView()
}
} else {
Text(connectionStatus)
.font(.caption.monospaced())
.foregroundStyle(.secondary)
}
}
}

View File

@ -12,28 +12,22 @@ struct MainTerminalView: View {
@State private var terminalVC = TerminalViewController()
@State private var connection: SessionConnection? = nil
@State private var statusText = "Connecting…"
@State private var currentPiState: PiState? = nil
@State private var sessionName = ""
@State private var showSwitcher = false
@State private var cancellables = Set<AnyCancellable>()
@EnvironmentObject var appState: AppState
var body: some View {
VStack(spacing: 0) {
// Status bar
HStack {
Text(statusText)
.font(.caption.monospaced())
.foregroundStyle(.secondary)
Spacer()
Button("Unpair") {
appState.unpair()
}
.font(.caption)
.foregroundStyle(.red)
}
.padding(.horizontal, 12)
.padding(.vertical, 6)
.background(Color(uiColor: .systemBackground))
Divider()
StatusBar(
sessionName: sessionName,
connectionStatus: statusText,
piState: $currentPiState,
onSwitcher: { showSwitcher = true },
onUnpair: { appState.unpair() }
)
// Terminal
TerminalViewRepresentable(controller: terminalVC)
@ -129,6 +123,24 @@ struct MainTerminalView: View {
}
.store(in: &cancellables)
// Wire stateEvents currentPiState
conn.stateEvents
.compactMap { event -> PiState? in
if case .state(let s, _, _) = event { return s } else { return nil }
}
.receive(on: DispatchQueue.main)
.sink { state in currentPiState = state }
.store(in: &cancellables)
// Wire stateEvents sessionName
conn.stateEvents
.compactMap { event -> String? in
if case .sessionMeta(let name, _, _) = event { return name } else { return nil }
}
.receive(on: DispatchQueue.main)
.sink { name in sessionName = name }
.store(in: &cancellables)
connection = conn
let lastSeq: UInt64? = conn.scrollback.sizeBytes > 0