diff --git a/apps/calendar-macos/CompassData/Sources/CompassData/CommandPalette/CommandPaletteCatalog.swift b/apps/calendar-macos/CompassData/Sources/CompassData/CommandPalette/CommandPaletteCatalog.swift index 48cbf72aab..e609ca4746 100644 --- a/apps/calendar-macos/CompassData/Sources/CompassData/CommandPalette/CommandPaletteCatalog.swift +++ b/apps/calendar-macos/CompassData/Sources/CompassData/CommandPalette/CommandPaletteCatalog.swift @@ -170,6 +170,16 @@ public enum CommandPaletteCatalog { isTrialing: Bool ) -> [CommandPaletteSection] { var items: [CommandPaletteItem] = [ + CommandPaletteItem( + id: "undo", + label: "Undo", + shortcut: "mod+z", + keywords: ["revert", "back"]), + CommandPaletteItem( + id: "redo", + label: "Redo", + shortcut: "mod+shift+z", + keywords: ["repeat", "forward"]), CommandPaletteItem( id: "show-welcome-guide", label: "Show welcome guide", diff --git a/apps/calendar-macos/CompassData/Sources/CompassData/NativeCalendarOverlays.swift b/apps/calendar-macos/CompassData/Sources/CompassData/NativeCalendarOverlays.swift index fe419afbd0..bd2b335a5e 100644 --- a/apps/calendar-macos/CompassData/Sources/CompassData/NativeCalendarOverlays.swift +++ b/apps/calendar-macos/CompassData/Sources/CompassData/NativeCalendarOverlays.swift @@ -130,6 +130,10 @@ extension NativeCalendarRootModel { requestLogout() case "subscribe": handleShortcut(.otherSubscribe) + case "undo": + undoLastChange() + case "redo": + redoLastChange() default: if id.hasPrefix("event-") { let eventId = String(id.dropFirst("event-".count)) diff --git a/apps/calendar-macos/CompassData/Tests/CompassDataTests/CommandPaletteActionsTests.swift b/apps/calendar-macos/CompassData/Tests/CompassDataTests/CommandPaletteActionsTests.swift index 0570c16736..a92377fc3c 100644 --- a/apps/calendar-macos/CompassData/Tests/CompassDataTests/CommandPaletteActionsTests.swift +++ b/apps/calendar-macos/CompassData/Tests/CompassDataTests/CommandPaletteActionsTests.swift @@ -101,6 +101,14 @@ final class CommandPaletteActionsTests: XCTestCase { XCTAssertFalse(model.commandPaletteStore.recentCommandIds.contains("recent-today")) } + func testMoreSectionIncludesUndoAndRedoRows() async throws { + let model = try await makeModel() + model.commandPaletteStore.open() + let ids = Set(model.filteredPaletteSections().flatMap(\.items).map(\.id)) + XCTAssertTrue(ids.contains("undo")) + XCTAssertTrue(ids.contains("redo")) + } + func testAboutInvokesNativePresentation() async throws { let model = try await makeModel() var presented = false diff --git a/apps/calendar-macos/CompassUI/Sources/CompassUI/CommandPalette/CommandPaletteOverlay.swift b/apps/calendar-macos/CompassUI/Sources/CompassUI/CommandPalette/CommandPaletteOverlay.swift index 9668c3bae6..b6459469bb 100644 --- a/apps/calendar-macos/CompassUI/Sources/CompassUI/CommandPalette/CommandPaletteOverlay.swift +++ b/apps/calendar-macos/CompassUI/Sources/CompassUI/CommandPalette/CommandPaletteOverlay.swift @@ -1,4 +1,5 @@ import CompassData +import CompassKit import SwiftUI public struct CommandPaletteOverlay: View { @@ -111,19 +112,42 @@ public struct CommandPaletteOverlay: View { } Spacer() if let shortcut = item.shortcut { - Text(shortcut) - .font(.rubik(12)) + Text(paletteShortcutLabel(shortcut)) + .font(.rubik(11)) .foregroundStyle(theme.textMutedColor) } } .padding(.vertical, 6) .padding(.horizontal, 8) - .background(selected ? theme.accentColor.opacity(0.18) : theme.surfaceColor) + .background(selected ? theme.accentColor.opacity(0.12) : theme.surfaceColor) .clipShape(RoundedRectangle(cornerRadius: 8)) + .overlay { + if selected { + RoundedRectangle(cornerRadius: 8) + .stroke(theme.accentColor, lineWidth: 1) + } + } } .buttonStyle(.plain) .accessibilityValue(selected ? "Selected" : "Not selected") .accessibilityAddTraits(selected ? .isSelected : []) .accessibilityIdentifier("compass-native-palette-item-\(item.id)") } + + private func paletteShortcutLabel(_ shortcut: String) -> String { + let normalized = shortcut + .replacingOccurrences(of: "mod+", with: "Mod+") + .replacingOccurrences(of: "shift+", with: "Shift+") + .replacingOccurrences(of: "alt+", with: "Alt+") + let parts = normalized.split(separator: "+").map { part -> String in + let token = String(part) + switch token.lowercased() { + case "mod": return "Mod" + case "shift": return "Shift" + case "alt": return "Alt" + default: return token.count == 1 ? token.uppercased() : token + } + } + return KeycapGlyphs.display(parts) + } }