Skip to content

Commit 550586a

Browse files
author
itsdodobitch
committed
Prepare Hermes Desktop v1.1.1
1 parent d7f977b commit 550586a

6 files changed

Lines changed: 127 additions & 21 deletions

File tree

Sources/HermesDesktop/App/AppState.swift

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ final class AppState: ObservableObject {
109109
private var sessionScrollOffsets: [String: CGFloat] = [:]
110110
private var sessionMessageSignature = SessionMessageSignature(messages: [])
111111
private var connectionTestRequestID: UUID?
112+
private var overviewRefreshWorkspaceFingerprint: String?
112113
private var hasPerformedAutomaticUpdateCheck = false
113114
private let automaticUpdateCheckInterval: TimeInterval = 24 * 60 * 60
114115
private var statusTask: Task<Void, Never>?
@@ -657,9 +658,22 @@ final class AppState: ObservableObject {
657658

658659
func refreshOverview(manual: Bool = false) async {
659660
guard let profile = activeConnection else { return }
661+
let workspaceFingerprint = profile.workspaceScopeFingerprint
660662
if manual {
661663
guard !isRefreshingOverview, !isBusy else { return }
662-
isRefreshingOverview = true
664+
} else {
665+
guard overviewRefreshWorkspaceFingerprint != workspaceFingerprint else { return }
666+
}
667+
668+
overviewRefreshWorkspaceFingerprint = workspaceFingerprint
669+
isRefreshingOverview = true
670+
671+
defer {
672+
if overviewRefreshWorkspaceFingerprint == workspaceFingerprint {
673+
isBusy = false
674+
isRefreshingOverview = false
675+
overviewRefreshWorkspaceFingerprint = nil
676+
}
663677
}
664678

665679
do {
@@ -669,16 +683,8 @@ final class AppState: ObservableObject {
669683
guard isActiveWorkspace(profile) else { return }
670684
overview = discovery
671685
lastOverviewRefreshedAt = Date()
672-
isBusy = false
673-
if manual {
674-
isRefreshingOverview = false
675-
}
676686
} catch {
677687
guard isActiveWorkspace(profile) else { return }
678-
isBusy = false
679-
if manual {
680-
isRefreshingOverview = false
681-
}
682688
overview = nil
683689
overviewError = error.localizedDescription
684690
setStatusMessage(L10n.string("Unable to refresh remote discovery"))
@@ -2757,8 +2763,10 @@ final class AppState: ObservableObject {
27572763
private func resetWorkspaceStateForConnectionChange(closeTerminalTabs: Bool = true) {
27582764
isBusy = false
27592765
connectionTestRequestID = nil
2766+
overviewRefreshWorkspaceFingerprint = nil
27602767
overview = nil
27612768
overviewError = nil
2769+
lastOverviewRefreshedAt = nil
27622770
isRefreshingOverview = false
27632771
sessions = []
27642772
clearSessionMessages()

Sources/HermesDesktop/Resources/en.lproj/Localizable.strings

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,8 @@
10041004
"Press Run Diagnostics or Test Connection to check this host." = "Press Run Diagnostics or Test Connection to check this host.";
10051005
"Refresh Profiles" = "Refresh Profiles";
10061006
"Refresh Settings after the host is reachable." = "Refresh Settings after the host is reachable.";
1007+
"Remove host?" = "Remove host?";
1008+
"Remove Host" = "Remove Host";
10071009
"Run Diagnostics" = "Run Diagnostics";
10081010
"See recent conversations and history." = "See recent conversations and history.";
10091011
"Settings" = "Settings";
@@ -1021,7 +1023,9 @@
10211023
"Test Connection" = "Test Connection";
10221024
"Terminal Appearance" = "Terminal Appearance";
10231025
"This permanently deletes %@ from the host. This cannot be undone." = "This permanently deletes %@ from the host. This cannot be undone.";
1026+
"This removes the host from Hermes Desktop. Remote files and Hermes profiles on the host are not deleted." = "This removes the host from Hermes Desktop. Remote files and Hermes profiles on the host are not deleted.";
10241027
"This removes the profile directory from the host." = "This removes the profile directory from the host.";
1028+
"“%@” will be removed from Hermes Desktop. Remote files and Hermes profiles on the host are not deleted." = "“%@” will be removed from Hermes Desktop. Remote files and Hermes profiles on the host are not deleted.";
10251029
"Choose the app appearance." = "Choose the app appearance.";
10261030
"View Sessions" = "View Sessions";
10271031
"Track Profile" = "Track Profile";

Sources/HermesDesktop/Resources/ru.lproj/Localizable.strings

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,8 @@
10041004
"Press Run Diagnostics or Test Connection to check this host." = "Нажмите «Запустить диагностику» или «Проверить подключение», чтобы проверить этот хост.";
10051005
"Refresh Profiles" = "Обновить профили";
10061006
"Refresh Settings after the host is reachable." = "Обновите настройки после того, как хост станет доступен.";
1007+
"Remove host?" = "Remove host?";
1008+
"Remove Host" = "Remove Host";
10071009
"Run Diagnostics" = "Запустить диагностику";
10081010
"See recent conversations and history." = "Просматривайте недавние диалоги и историю.";
10091011
"Settings" = "Настройки";
@@ -1021,7 +1023,9 @@
10211023
"Test Connection" = "Проверить подключение";
10221024
"Terminal Appearance" = "Внешний вид терминала";
10231025
"This permanently deletes %@ from the host. This cannot be undone." = "Это навсегда удалит %@ с хоста. Действие нельзя отменить.";
1026+
"This removes the host from Hermes Desktop. Remote files and Hermes profiles on the host are not deleted." = "This removes the host from Hermes Desktop. Remote files and Hermes profiles on the host are not deleted.";
10241027
"This removes the profile directory from the host." = "Это удалит папку профиля с хоста.";
1028+
"“%@” will be removed from Hermes Desktop. Remote files and Hermes profiles on the host are not deleted." = "“%@” will be removed from Hermes Desktop. Remote files and Hermes profiles on the host are not deleted.";
10251029
"Choose the app appearance." = "Выберите внешний вид приложения.";
10261030
"View Sessions" = "Открыть сессии";
10271031
"Track Profile" = "Отслеживать профиль";

Sources/HermesDesktop/Resources/zh-Hans.lproj/Localizable.strings

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,8 @@
10041004
"Press Run Diagnostics or Test Connection to check this host." = "点击“运行诊断”或“测试连接”来检查此主机。";
10051005
"Refresh Profiles" = "刷新配置";
10061006
"Refresh Settings after the host is reachable." = "主机可访问后刷新设置。";
1007+
"Remove host?" = "Remove host?";
1008+
"Remove Host" = "Remove Host";
10071009
"Run Diagnostics" = "运行诊断";
10081010
"See recent conversations and history." = "查看最近的对话和历史记录。";
10091011
"Settings" = "设置";
@@ -1021,7 +1023,9 @@
10211023
"Test Connection" = "测试连接";
10221024
"Terminal Appearance" = "终端外观";
10231025
"This permanently deletes %@ from the host. This cannot be undone." = "这会从主机永久删除 %@。此操作无法撤销。";
1026+
"This removes the host from Hermes Desktop. Remote files and Hermes profiles on the host are not deleted." = "This removes the host from Hermes Desktop. Remote files and Hermes profiles on the host are not deleted.";
10241027
"This removes the profile directory from the host." = "这会从主机删除配置目录。";
1028+
"“%@” will be removed from Hermes Desktop. Remote files and Hermes profiles on the host are not deleted." = "“%@” will be removed from Hermes Desktop. Remote files and Hermes profiles on the host are not deleted.";
10251029
"Choose the app appearance." = "选择应用外观。";
10261030
"View Sessions" = "查看会话";
10271031
"Track Profile" = "跟踪配置";

Sources/HermesDesktop/Views/Connections/ConnectionsView.swift

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@ struct ConnectionsView: View {
66
@Environment(\.openURL) private var openURL
77
@EnvironmentObject private var appState: AppState
88

9-
@State private var editingConnection = ConnectionProfile()
10-
@State private var editorPresentationID = UUID()
11-
@State private var isPresentingEditor = false
12-
@State private var editingExistingConnection = false
9+
@State private var editorPresentation: ConnectionEditorPresentation?
1310
@State private var isPresentingProfiles = false
1411
@State private var inspectorTab: SettingsInspectorTab = .overview
1512
@State private var profilePendingDelete: RemoteHermesProfile?
1613
@State private var profilePendingStopTracking: RemoteHermesProfile?
14+
@State private var hostPendingRemoval: ConnectionProfile?
1715

1816
private let documentationURL = URL(string: "https://dodo-reach.github.io/hermes-desktop/")!
1917

@@ -42,14 +40,13 @@ struct ConnectionsView: View {
4240
}
4341
}
4442
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
45-
.sheet(isPresented: $isPresentingEditor) {
43+
.sheet(item: $editorPresentation) { presentation in
4644
ConnectionEditorSheet(
47-
connection: editingConnection,
48-
isEditing: editingExistingConnection
45+
connection: presentation.connection,
46+
isEditing: presentation.isEditing
4947
) { updatedConnection in
5048
appState.saveConnection(updatedConnection)
5149
}
52-
.id(editorPresentationID)
5350
}
5451
.sheet(isPresented: $isPresentingProfiles) {
5552
ManageProfilesSheet(
@@ -96,6 +93,28 @@ struct ConnectionsView: View {
9693
} message: {
9794
Text(deleteProfileConfirmationMessage)
9895
}
96+
.alert(
97+
L10n.string("Remove host?"),
98+
isPresented: Binding(
99+
get: { hostPendingRemoval != nil },
100+
set: { if !$0 { hostPendingRemoval = nil } }
101+
)
102+
) {
103+
Button(L10n.string("Remove Host"), role: .destructive) {
104+
guard let host = hostPendingRemoval else { return }
105+
hostPendingRemoval = nil
106+
if editorPresentation?.connection.id == host.id {
107+
editorPresentation = nil
108+
}
109+
appState.deleteConnection(host)
110+
}
111+
112+
Button(L10n.string("Cancel"), role: .cancel) {
113+
hostPendingRemoval = nil
114+
}
115+
} message: {
116+
Text(removeHostConfirmationMessage)
117+
}
99118
.alert(
100119
L10n.string("Stop tracking this profile?"),
101120
isPresented: Binding(
@@ -466,6 +485,14 @@ struct ConnectionsView: View {
466485
.buttonStyle(.bordered)
467486
.controlSize(.small)
468487

488+
Button(role: .destructive) {
489+
hostPendingRemoval = activeConnection
490+
} label: {
491+
Label(L10n.string("Remove Host"), systemImage: "trash")
492+
}
493+
.buttonStyle(.bordered)
494+
.controlSize(.small)
495+
469496
hostSelectionMenu
470497

471498
Button {
@@ -639,6 +666,13 @@ struct ConnectionsView: View {
639666
return L10n.string("This permanently deletes %@ from the host. This cannot be undone.", profilePendingDelete.path)
640667
}
641668

669+
private var removeHostConfirmationMessage: String {
670+
guard let hostPendingRemoval else {
671+
return L10n.string("This removes the host from Hermes Desktop. Remote files and Hermes profiles on the host are not deleted.")
672+
}
673+
return L10n.string("“%@” will be removed from Hermes Desktop. Remote files and Hermes profiles on the host are not deleted.", hostPendingRemoval.label)
674+
}
675+
642676
private func workspaceFields(_ connection: ConnectionProfile) -> [HermesInspectorField] {
643677
[
644678
HermesInspectorField(label: "Workspace", value: connection.label, emphasizeValue: true),
@@ -674,10 +708,7 @@ struct ConnectionsView: View {
674708
}
675709

676710
private func presentEditor(for connection: ConnectionProfile, isEditing: Bool) {
677-
editingConnection = connection
678-
editingExistingConnection = isEditing
679-
editorPresentationID = UUID()
680-
isPresentingEditor = true
711+
editorPresentation = ConnectionEditorPresentation(connection: connection, isEditing: isEditing)
681712
}
682713

683714
private func presentPendingNewConnectionEditorIfNeeded() {
@@ -687,6 +718,12 @@ struct ConnectionsView: View {
687718
}
688719
}
689720

721+
private struct ConnectionEditorPresentation: Identifiable {
722+
let id = UUID()
723+
let connection: ConnectionProfile
724+
let isEditing: Bool
725+
}
726+
690727
private enum SettingsInspectorTab: String, CaseIterable, Identifiable {
691728
case overview
692729
case diagnostics

docs/releases/RELEASE-v1.1.1.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Hermes Desktop v1.1.1
2+
3+
`v1.1.1` is a focused Settings and connection-hardening release.
4+
5+
It keeps the same direct SSH-first model from `v1.1.0`, while making host
6+
management calmer when you move between network paths, SSH aliases, or saved
7+
hosts. The selected Hermes host remains the source of truth: no gateway service,
8+
no local mirror, and no background sync layer.
9+
10+
## What Changed
11+
12+
- Settings now serializes automatic host discovery refreshes for the active
13+
workspace, reducing duplicate probes and avoiding misleading transient
14+
connection-health states.
15+
- Switching hosts now clears the previous "Last Checked" timestamp so Settings
16+
always reflects the currently selected host.
17+
- Host management now includes a confirmed "Remove Host" action. Removing a host
18+
only deletes the saved local connection from Hermes Desktop; remote Hermes
19+
files and profiles are left untouched.
20+
- The first "Edit Host" action now opens the selected host details immediately,
21+
so the sheet shows the connection being edited from the first attempt.
22+
23+
## Compatibility
24+
25+
- macOS 14 or newer
26+
- SSH from this Mac to the Hermes host must already work without interactive
27+
prompts
28+
- `python3` must be available on the host
29+
- Chat and Terminal resume require the remote `hermes` CLI on the
30+
non-interactive SSH `PATH`
31+
- public releases are still ad-hoc signed and not notarized by Apple
32+
33+
## Still True
34+
35+
- Hermes Desktop connects directly over SSH
36+
- the Hermes host remains the source of truth
37+
- sessions, Kanban, cron jobs, files, skills, usage, Chat, and Terminal all stay
38+
anchored to the selected host and profile
39+
- workflow presets remain local launch helpers, not a second transport model or
40+
synchronization layer
41+
42+
## Notes
43+
44+
- universal macOS build for Apple Silicon and Intel
45+
- ad-hoc signed and not notarized yet, so first launch may still require
46+
right-click -> Open / Open Anyway
47+
- release archive: `HermesDesktop.app.zip`
48+
- checksum: `HermesDesktop.app.zip.sha256`
49+
- manifest: `HermesDesktop.app.zip.manifest.json`

0 commit comments

Comments
 (0)