Fix session rename not updating across views

After rename:
- Update selectedSession so detail header refreshes immediately
- Update sessionPreviews so previewFor() returns the new title
- Dashboard now observes HermesFileWatcher and reloads on DB changes
- Chat session menu reloads via file watcher (persists across nav)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alan Wizemann
2026-03-31 12:01:20 -04:00
parent e4d5bb0364
commit a800a630a8
3 changed files with 53 additions and 1 deletions
@@ -2,6 +2,7 @@ import SwiftUI
struct ChatView: View {
@Environment(ChatViewModel.self) private var viewModel
@Environment(HermesFileWatcher.self) private var fileWatcher
var body: some View {
VStack(spacing: 0) {
@@ -11,6 +12,9 @@ struct ChatView: View {
}
.navigationTitle("Chat")
.task { await viewModel.loadRecentSessions() }
.onChange(of: fileWatcher.lastChangeDate) {
Task { await viewModel.loadRecentSessions() }
}
}
private var toolbar: some View {
@@ -36,6 +40,10 @@ struct ChatView: View {
Spacer()
if viewModel.hasActiveProcess {
voiceControls
}
if !viewModel.hermesBinaryExists {
Label("Hermes binary not found", systemImage: "exclamationmark.triangle")
.font(.caption)
@@ -80,6 +88,41 @@ struct ChatView: View {
.padding(.vertical, 6)
}
private var voiceControls: some View {
HStack(spacing: 8) {
Button {
viewModel.toggleVoice()
} label: {
HStack(spacing: 4) {
Image(systemName: viewModel.voiceEnabled ? "mic.fill" : "mic.slash")
.foregroundStyle(viewModel.voiceEnabled ? .green : .secondary)
Text(viewModel.voiceEnabled ? "Voice On" : "Voice Off")
.font(.caption)
.foregroundStyle(viewModel.voiceEnabled ? .primary : .secondary)
}
}
.buttonStyle(.plain)
.help("Toggle voice mode (/voice)")
if viewModel.voiceEnabled {
Button {
viewModel.pushToTalk()
} label: {
HStack(spacing: 4) {
Image(systemName: viewModel.isRecording ? "waveform.circle.fill" : "waveform.circle")
.foregroundStyle(viewModel.isRecording ? .red : .accentColor)
.symbolEffect(.pulse, isActive: viewModel.isRecording)
Text(viewModel.isRecording ? "Recording..." : "Push to Talk")
.font(.caption)
}
}
.buttonStyle(.plain)
.help("Push to talk (Ctrl+B)")
.keyboardShortcut("b", modifiers: .control)
}
}
}
@ViewBuilder
private var terminalArea: some View {
if let terminal = viewModel.terminalView {
@@ -3,6 +3,7 @@ import SwiftUI
struct DashboardView: View {
@State private var viewModel = DashboardViewModel()
@Environment(AppCoordinator.self) private var coordinator
@Environment(HermesFileWatcher.self) private var fileWatcher
var body: some View {
ScrollView {
@@ -16,6 +17,9 @@ struct DashboardView: View {
}
.navigationTitle("Dashboard")
.task { await viewModel.load() }
.onChange(of: fileWatcher.lastChangeDate) {
Task { await viewModel.load() }
}
}
private var statusSection: some View {
@@ -83,7 +83,7 @@ final class SessionsViewModel {
let result = runHermes(["sessions", "rename", sessionId, title])
if result.exitCode == 0 {
if let idx = sessions.firstIndex(where: { $0.id == sessionId }) {
sessions[idx] = HermesSession(
let updated = HermesSession(
id: sessions[idx].id, source: sessions[idx].source,
userId: sessions[idx].userId, model: sessions[idx].model,
title: title, parentSessionId: sessions[idx].parentSessionId,
@@ -94,7 +94,12 @@ final class SessionsViewModel {
cacheWriteTokens: sessions[idx].cacheWriteTokens,
estimatedCostUSD: sessions[idx].estimatedCostUSD
)
sessions[idx] = updated
if selectedSession?.id == sessionId {
selectedSession = updated
}
}
sessionPreviews[sessionId] = title
}
showRenameSheet = false
renameSessionId = nil