Repository navigation
Add workspace and tab notification mute menus #6658
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c5f48d2
247285a
5717d94
d3719e2
0f8c77d
f93e8f9
805c30c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -192,96 +192,6 @@ struct CmuxResolvedWorkspaceGroupConfig: Sendable, Equatable { | |
| let newWorkspacePlacement: WorkspaceGroupNewPlacement? | ||
| } | ||
|
|
||
| enum CmuxNotificationHooksMode: String, Codable, Sendable, Hashable { | ||
| case append | ||
| case replace | ||
| } | ||
|
|
||
| struct CmuxNotificationConfigDefinition: Codable, Sendable, Hashable { | ||
| var hooks: [CmuxNotificationHookDefinition]? | ||
| var hooksMode: CmuxNotificationHooksMode? | ||
|
|
||
| private enum CodingKeys: String, CodingKey { | ||
| case hooks | ||
| case hooksMode | ||
| } | ||
| } | ||
|
|
||
| struct CmuxNotificationHookDefinition: Codable, Sendable, Hashable { | ||
| static let defaultTimeoutSeconds: TimeInterval = 20 | ||
|
|
||
| var id: String | ||
| var command: String | ||
| var timeoutSeconds: TimeInterval? | ||
| var enabled: Bool | ||
|
|
||
| private enum CodingKeys: String, CodingKey { | ||
| case id | ||
| case command | ||
| case timeoutSeconds | ||
| case enabled | ||
| } | ||
|
|
||
| init( | ||
| id: String, | ||
| command: String, | ||
| timeoutSeconds: TimeInterval? = nil, | ||
| enabled: Bool = true | ||
| ) { | ||
| self.id = id | ||
| self.command = command | ||
| self.timeoutSeconds = timeoutSeconds | ||
| self.enabled = enabled | ||
| } | ||
|
|
||
| init(from decoder: Decoder) throws { | ||
| let container = try decoder.container(keyedBy: CodingKeys.self) | ||
| let decodedID = try Self.requiredTrimmedString(forKey: .id, in: container) | ||
| let decodedCommand = try Self.requiredTrimmedString(forKey: .command, in: container) | ||
| let decodedTimeout = try container.decodeIfPresent(TimeInterval.self, forKey: .timeoutSeconds) | ||
| if let decodedTimeout, !decodedTimeout.isFinite || decodedTimeout <= 0 { | ||
| throw DecodingError.dataCorruptedError( | ||
| forKey: .timeoutSeconds, | ||
| in: container, | ||
| debugDescription: "timeoutSeconds must be greater than 0" | ||
| ) | ||
| } | ||
|
|
||
| id = decodedID | ||
| command = decodedCommand | ||
| timeoutSeconds = decodedTimeout | ||
| enabled = try container.decodeIfPresent(Bool.self, forKey: .enabled) ?? true | ||
| } | ||
|
|
||
| func encode(to encoder: Encoder) throws { | ||
| var container = encoder.container(keyedBy: CodingKeys.self) | ||
| try container.encode(id, forKey: .id) | ||
| try container.encode(command, forKey: .command) | ||
| try container.encodeIfPresent(timeoutSeconds, forKey: .timeoutSeconds) | ||
| try container.encode(enabled, forKey: .enabled) | ||
| } | ||
|
|
||
| var resolvedTimeoutSeconds: TimeInterval { | ||
| timeoutSeconds ?? Self.defaultTimeoutSeconds | ||
| } | ||
|
|
||
| private static func requiredTrimmedString( | ||
| forKey key: CodingKeys, | ||
| in container: KeyedDecodingContainer<CodingKeys> | ||
| ) throws -> String { | ||
| let value = try container.decode(String.self, forKey: key) | ||
| .trimmingCharacters(in: .whitespacesAndNewlines) | ||
| guard !value.isEmpty else { | ||
| throw DecodingError.dataCorruptedError( | ||
| forKey: key, | ||
| in: container, | ||
| debugDescription: "\(key.stringValue) must not be blank" | ||
| ) | ||
| } | ||
| return value | ||
| } | ||
| } | ||
|
|
||
| struct CmuxResolvedNotificationHook: Sendable, Hashable { | ||
| let id: String | ||
| let command: String | ||
|
|
@@ -1896,6 +1806,7 @@ final class CmuxConfigStore: ObservableObject { | |
| @Published private(set) var workspaceGroupConfigs: [CmuxResolvedWorkspaceGroupConfig] = [] | ||
| @Published private(set) var surfaceTabBarButtons: [CmuxSurfaceTabBarButton] = CmuxSurfaceTabBarButton.defaults | ||
| @Published private(set) var notificationHooks: [CmuxResolvedNotificationHook] = [] | ||
| @Published private(set) var notificationMuteMenuOptions: [NotificationMuteMenuOption] = NotificationMuteMenuOption.defaultOptions | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift Avoid adding new This adds new Combine-based state ( As per coding guidelines, “Do not use 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| @Published private(set) var configurationIssues: [CmuxConfigIssue] = [] | ||
| @Published private(set) var configRevision: UInt64 = 0 | ||
|
|
||
|
|
@@ -2293,6 +2204,10 @@ final class CmuxConfigStore: ObservableObject { | |
| entry.result.config.map { (path: entry.path, config: $0) } | ||
| } | ||
| ) | ||
| let resolvedNotificationMuteMenuOptions = resolveNotificationMuteMenuOptions( | ||
| localConfig: localConfig, | ||
| globalConfig: globalConfig | ||
| ) | ||
|
|
||
| loadedCommands = commands | ||
| loadedActions = resolvedActions | ||
|
|
@@ -2318,6 +2233,7 @@ final class CmuxConfigStore: ObservableObject { | |
| surfaceTabBarWorkspaceCommands = resolvedWorkspaceButtons.workspaceCommands | ||
| surfaceTabBarButtons = resolvedWorkspaceButtons.buttons | ||
| notificationHooks = resolvedNotificationHooks | ||
| notificationMuteMenuOptions = resolvedNotificationMuteMenuOptions | ||
| resolvedNewWorkspaceActionCache = resolvedNewWorkspaceAction.action | ||
| resolvedNewWorkspaceCommandCache = resolvedNewWorkspaceAction.command | ||
| if let issue = resolvedNewWorkspaceAction.issue { | ||
|
|
@@ -2346,6 +2262,16 @@ final class CmuxConfigStore: ObservableObject { | |
| return fallbackLocalPath.map { [$0] } ?? [] | ||
| } | ||
|
|
||
| private func resolveNotificationMuteMenuOptions( | ||
| localConfig: CmuxConfigFile?, | ||
| globalConfig: CmuxConfigFile? | ||
| ) -> [NotificationMuteMenuOption] { | ||
| NotificationMuteMenuOption.options( | ||
| configuredDurations: localConfig?.notifications?.muteDurations ?? | ||
| globalConfig?.notifications?.muteDurations | ||
| ) | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| } | ||
|
|
||
| private func resolveNotificationHooks( | ||
| globalConfig: CmuxConfigFile?, | ||
| localConfigs: [(path: String, config: CmuxConfigFile)] | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All ten new mute/unmute string keys (
contextMenu.muteWorkspaceNotifications,contextMenu.muteWorkspacesNotifications,contextMenu.unmuteWorkspaceNotifications,contextMenu.unmuteWorkspacesNotifications,notificationMute.duration.*,terminalContextMenu.*) only carryenandjaentries. Existing catalog strings already includezh-Hans,zh-Hant,ko,de,es,fr,it,da,pl,ru,bs,ar, and potentially others (visible starting at line 241). Users in those locales will see raw English fallback text in the mute menus and submenu items.Rule Used: Flag production user-facing text that is not fully... (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!