fix: arm held Command on activation and animate number overlays

This commit is contained in:
2026-10-13 22:31:32 +02:00
parent 77f62b8d41
commit 006c7e7dd0
5 changed files with 268 additions and 77 deletions
+1 -1
View File
@@ -33,7 +33,7 @@ Open `Package.swift` in Xcode to edit the project. Use the bundled app above to
- Type to filter applications, calculate an expression, or convert units and currencies; the first result is selected automatically. - Type to filter applications, calculate an expression, or convert units and currencies; the first result is selected automatically.
- Up/Down or Control-K/Control-J changes the selection without leaving search or modifying its text. - Up/Down or Control-K/Control-J changes the selection without leaving search or modifying its text.
- Return, keypad Enter, the footer button, or a double-click opens the selected application or copies a calculator answer and dismisses the launcher. - Return, keypad Enter, the footer button, or a double-click opens the selected application or copies a calculator answer and dismisses the launcher.
- Hold Command alone for one second to show numbered badges at the right edge of the first nine application results. While still holding Command, press 1–9 to open that result or bring its running instance forward through the normal launch/history path. Numbers refer to the current ranked search results, not the selection or a scroll-relative position. Releasing Command, pressing another key/modifier, losing focus, dismissing, or opening Actions/uninstall review cancels the hints. Calculator/currency results and modal sheets do not enable numbered launching; missing result numbers do nothing. - Hold Command alone for one second to show numbered badges at the right edge of the first nine application results. Command already held when opening the launcher also counts: press Command-Space, release Space, and keep Command held. Newly discovered results start the delay once eligible. While still holding Command, press 1–9 to open that result or bring its running instance forward through the normal launch/history path. Numbers refer to the current ranked search results, not the selection or a scroll-relative position. Badges overlay the existing Application text, fading its right edge with an alpha mask while sliding in 14 points from the right over 160 milliseconds; Reduce Motion switches immediately. Releasing Command, pressing another key/modifier, losing focus, dismissing, or opening Actions/uninstall review cancels the hints. A cancelled hold does not restart just because results refresh or focus returns; release Command or reopen the launcher to start a new hold. Calculator/currency results and modal sheets do not enable numbered launching; missing result numbers do nothing.
- Command-K or the bottom-right Actions button opens a searchable selector for the selected application. **Open** is first and selected by default. Up/Down or Control-K/Control-J selects an action; Return executes it. Escape closes the selector and returns focus to search. - Command-K or the bottom-right Actions button opens a searchable selector for the selected application. **Open** is first and selected by default. Up/Down or Control-K/Control-J selects an action; Return executes it. Escape closes the selector and returns focus to search.
- Escape or Command-W dismisses the launcher and restores the previously active application. Switching focus away dismisses it without stealing focus back. - Escape or Command-W dismisses the launcher and restores the previously active application. Switching focus away dismisses it without stealing focus back.
- Reopen from Finder to show it again with an empty search. Dismissal leaves the process running; Command-Q quits it. - Reopen from Finder to show it again with an empty search. Dismissal leaves the process running; Command-Q quits it.
+215 -73
View File
@@ -94,10 +94,12 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
} }
func present() { func present() {
(window as? LauncherPanel)?.beginNumberHintSession()
if launcherFrameBeforeReview != nil { if launcherFrameBeforeReview != nil {
showWindow(nil) showWindow(nil)
NSApp.activate() NSApp.activate()
window?.makeKeyAndOrderFront(nil) window?.makeKeyAndOrderFront(nil)
(window as? LauncherPanel)?.sampleNumberHints()
return return
} }
let frontmost = NSWorkspace.shared.frontmostApplication let frontmost = NSWorkspace.shared.frontmostApplication
@@ -112,6 +114,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
NSApp.activate() NSApp.activate()
window?.makeKeyAndOrderFront(nil) window?.makeKeyAndOrderFront(nil)
window?.makeFirstResponder(searchField) window?.makeFirstResponder(searchField)
(window as? LauncherPanel)?.sampleNumberHints()
(searchField.currentEditor() as? NSTextView)?.insertionPointColor = SunsetPalette.accent (searchField.currentEditor() as? NSTextView)?.insertionPointColor = SunsetPalette.accent
refreshApplications() refreshApplications()
} }
@@ -473,6 +476,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
statusLabel.stringValue = results.count == 1 ? "1 application" : "\(results.count) applications" statusLabel.stringValue = results.count == 1 ? "1 application" : "\(results.count) applications"
} }
updateOpenButton() updateOpenButton()
(window as? LauncherPanel)?.sampleNumberHints()
} }
func controlTextDidChange(_ notification: Notification) { func controlTextDidChange(_ notification: Notification) {
@@ -559,12 +563,10 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
return view return view
} }
private func configureNumberHint(_ cell: ApplicationCell, row: Int) { private func configureNumberHint(_ cell: ApplicationCell, row: Int, animated: Bool = false) {
let visible = (window as? LauncherPanel)?.numberHintsVisible == true let visible = (window as? LauncherPanel)?.numberHintsVisible == true
&& calculation == nil && row < 9 && results.indices.contains(row) && calculation == nil && row < 9 && results.indices.contains(row)
cell.numberHint.isHidden = !visible cell.setNumberHint(visible ? String(row + 1) : nil, animated: animated)
cell.kindLabel.isHidden = visible
if visible { cell.numberHint.setSingleKey(String(row + 1)) }
cell.setAccessibilityHelp(visible ? "Command-\(row + 1) opens this application" : nil) cell.setAccessibilityHelp(visible ? "Command-\(row + 1) opens this application" : nil)
} }
@@ -573,7 +575,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
guard rows.length > 0 else { return } guard rows.length > 0 else { return }
for row in rows.location..<(rows.location + rows.length) { for row in rows.location..<(rows.location + rows.length) {
if let cell = tableView.view(atColumn: 0, row: row, makeIfNecessary: false) as? ApplicationCell { if let cell = tableView.view(atColumn: 0, row: row, makeIfNecessary: false) as? ApplicationCell {
configureNumberHint(cell, row: row) configureNumberHint(cell, row: row, animated: true)
} }
} }
} }
@@ -581,48 +583,6 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
private func makeCell(identifier: NSUserInterfaceItemIdentifier) -> ApplicationCell { private func makeCell(identifier: NSUserInterfaceItemIdentifier) -> ApplicationCell {
let cell = ApplicationCell() let cell = ApplicationCell()
cell.identifier = identifier cell.identifier = identifier
let icon = NSImageView()
icon.imageScaling = .scaleProportionallyDown
icon.setAccessibilityElement(false)
let label = NSTextField(labelWithString: "")
label.font = .systemFont(ofSize: LauncherRowView.textSize, weight: .medium)
label.textColor = SunsetPalette.text
label.lineBreakMode = .byTruncatingTail
let location = cell.locationLabel
location.font = .systemFont(ofSize: 12)
location.textColor = SunsetPalette.secondaryText
location.lineBreakMode = .byTruncatingMiddle
location.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
location.isHidden = true
let title = NSStackView(views: [label, location])
title.orientation = .horizontal
title.alignment = .centerY
title.spacing = 8
let kind = cell.kindLabel
kind.font = .systemFont(ofSize: 14)
kind.textColor = SunsetPalette.secondaryText
kind.setContentCompressionResistancePriority(.required, for: .horizontal)
kind.setAccessibilityElement(false)
cell.imageView = icon
cell.textField = label
cell.numberHint.isHidden = true
for view in [icon, title, kind, cell.numberHint] {
view.translatesAutoresizingMaskIntoConstraints = false
cell.addSubview(view)
}
NSLayoutConstraint.activate([
icon.leadingAnchor.constraint(equalTo: cell.leadingAnchor, constant: 8),
icon.centerYAnchor.constraint(equalTo: cell.centerYAnchor),
icon.widthAnchor.constraint(equalToConstant: LauncherRowView.iconSize),
icon.heightAnchor.constraint(equalToConstant: LauncherRowView.iconSize),
title.leadingAnchor.constraint(equalTo: icon.trailingAnchor, constant: LauncherRowView.iconToTextSpacing),
title.centerYAnchor.constraint(equalTo: cell.centerYAnchor),
title.trailingAnchor.constraint(lessThanOrEqualTo: kind.leadingAnchor, constant: -12),
kind.centerYAnchor.constraint(equalTo: cell.centerYAnchor),
kind.trailingAnchor.constraint(equalTo: cell.trailingAnchor, constant: -8),
cell.numberHint.trailingAnchor.constraint(equalTo: cell.trailingAnchor, constant: -8),
cell.numberHint.centerYAnchor.constraint(equalTo: cell.centerYAnchor),
])
return cell return cell
} }
@@ -758,10 +718,153 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
} }
@MainActor @MainActor
private final class ApplicationCell: NSTableCellView { final class ApplicationCell: NSTableCellView {
private static let hintOffset: CGFloat = 14
private static let opaqueMask = [NSColor.black.cgColor, NSColor.black.cgColor]
private static let fadedMask = [NSColor.black.cgColor, NSColor.clear.cgColor]
private static let timing = CAMediaTimingFunction(name: .easeOut)
let locationLabel = NSTextField(labelWithString: "") let locationLabel = NSTextField(labelWithString: "")
let kindLabel = NSTextField(labelWithString: "Application") let kindLabel = NSTextField(labelWithString: "Application")
let numberHint = ShortcutIndicator("1") let numberHint = ShortcutIndicator("1", backgroundColor: SunsetPalette.background.withAlphaComponent(1))
private let kindFade = CAGradientLayer()
private var numberHintVisible = false
private var hintGeneration = 0
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
let icon = NSImageView()
icon.imageScaling = .scaleProportionallyDown
icon.setAccessibilityElement(false)
let label = NSTextField(labelWithString: "")
label.font = .systemFont(ofSize: LauncherRowView.textSize, weight: .medium)
label.textColor = SunsetPalette.text
label.lineBreakMode = .byTruncatingTail
locationLabel.font = .systemFont(ofSize: 12)
locationLabel.textColor = SunsetPalette.secondaryText
locationLabel.lineBreakMode = .byTruncatingMiddle
locationLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
locationLabel.isHidden = true
let title = NSStackView(views: [label, locationLabel])
title.orientation = .horizontal
title.alignment = .centerY
title.spacing = 8
kindLabel.font = .systemFont(ofSize: 14)
kindLabel.textColor = SunsetPalette.secondaryText
kindLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
kindLabel.setAccessibilityElement(false)
kindLabel.wantsLayer = true
imageView = icon
textField = label
numberHint.wantsLayer = true
numberHint.isHidden = true
numberHint.layer?.opacity = 0
numberHint.layer?.transform = CATransform3DMakeTranslation(Self.hintOffset, 0, 0)
kindFade.colors = Self.opaqueMask
kindFade.transform = CATransform3DMakeTranslation(Self.hintOffset, 0, 0)
// Added last so the badge overlays the label without changing the row's layout.
for view in [icon, title, kindLabel, numberHint] {
view.translatesAutoresizingMaskIntoConstraints = false
addSubview(view)
}
NSLayoutConstraint.activate([
icon.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
icon.centerYAnchor.constraint(equalTo: centerYAnchor),
icon.widthAnchor.constraint(equalToConstant: LauncherRowView.iconSize),
icon.heightAnchor.constraint(equalToConstant: LauncherRowView.iconSize),
title.leadingAnchor.constraint(equalTo: icon.trailingAnchor, constant: LauncherRowView.iconToTextSpacing),
title.centerYAnchor.constraint(equalTo: centerYAnchor),
title.trailingAnchor.constraint(lessThanOrEqualTo: kindLabel.leadingAnchor, constant: -12),
kindLabel.centerYAnchor.constraint(equalTo: centerYAnchor),
kindLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8),
numberHint.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8),
numberHint.centerYAnchor.constraint(equalTo: centerYAnchor),
])
}
required init?(coder: NSCoder) { fatalError("Conjure creates application cells programmatically") }
override func layout() {
super.layout()
updateKindFadeGeometry()
}
func setNumberHint(_ key: String?, animated: Bool = true) {
if let key { numberHint.setSingleKey(key) }
let visible = key != nil
let animate = animated && !NSWorkspace.shared.accessibilityDisplayShouldReduceMotion
if animate && visible == numberHintVisible { return }
numberHintVisible = visible
hintGeneration += 1
let generation = hintGeneration
if animate { layoutSubtreeIfNeeded() }
updateKindFadeGeometry()
guard let hintLayer = numberHint.layer, let labelLayer = kindLabel.layer else { return }
let hintPresentation = hintLayer.presentation() ?? hintLayer
let fadePresentation = kindFade.presentation() ?? kindFade
let hintOffset = hintPresentation.transform.m41
let hintOpacity = hintPresentation.opacity
let fadeOffset = fadePresentation.transform.m41
let fadeColors = fadePresentation.colors
let targetOffset: CGFloat = visible ? 0 : Self.hintOffset
let targetOpacity: Float = visible ? 1 : 0
let targetColors = visible ? Self.fadedMask : Self.opaqueMask
CATransaction.begin()
CATransaction.setDisableActions(true)
hintLayer.removeAllAnimations()
kindFade.removeAllAnimations()
kindLabel.isHidden = false
numberHint.isHidden = !(visible || animate)
labelLayer.mask = visible || animate ? kindFade : nil
hintLayer.transform = CATransform3DMakeTranslation(targetOffset, 0, 0)
hintLayer.opacity = targetOpacity
kindFade.transform = CATransform3DMakeTranslation(targetOffset, 0, 0)
kindFade.colors = targetColors
if animate {
if !visible {
CATransaction.setCompletionBlock { [weak self] in
MainActor.assumeIsolated {
guard let self, self.hintGeneration == generation, !self.numberHintVisible else { return }
CATransaction.begin()
CATransaction.setDisableActions(true)
self.numberHint.isHidden = true
self.kindLabel.layer?.mask = nil
CATransaction.commit()
}
}
}
animateLayer(hintLayer, key: "transform.translation.x", from: hintOffset, to: targetOffset)
animateLayer(hintLayer, key: "opacity", from: hintOpacity, to: targetOpacity)
animateLayer(kindFade, key: "transform.translation.x", from: fadeOffset, to: targetOffset)
animateLayer(kindFade, key: "colors", from: fadeColors, to: targetColors)
}
CATransaction.commit()
}
private func updateKindFadeGeometry() {
let padding = Self.hintOffset
let width = kindLabel.bounds.width + padding * 2
guard width > padding * 2 else { return }
let badgeLeading = numberHint.frame.minX - kindLabel.frame.minX + padding
CATransaction.begin()
CATransaction.setDisableActions(true)
// Extend the mask so its moving left edge never clips the start of the label.
kindFade.bounds = CGRect(x: 0, y: 0, width: width, height: kindLabel.bounds.height)
kindFade.position = CGPoint(x: kindLabel.bounds.midX, y: kindLabel.bounds.midY)
kindFade.startPoint = CGPoint(x: max(0, min(1, (badgeLeading - 18) / width)), y: 0.5)
kindFade.endPoint = CGPoint(x: max(0, min(1, (badgeLeading + 2) / width)), y: 0.5)
CATransaction.commit()
}
private func animateLayer(_ layer: CALayer, key: String, from: Any?, to: Any) {
let animation = CABasicAnimation(keyPath: key)
animation.fromValue = from
animation.toValue = to
animation.duration = 0.16
animation.timingFunction = Self.timing
layer.add(animation, forKey: key)
}
} }
@MainActor @MainActor
@@ -802,28 +905,74 @@ final class LauncherPanel: NSPanel {
private var commandHoldTask: Task<Void, Never>? private var commandHoldTask: Task<Void, Never>?
var numberHintsVisible: Bool { numberShortcuts.isVisible } var numberHintsVisible: Bool { numberShortcuts.isVisible }
private func commandOnly(_ event: NSEvent) -> Bool { private func commandOnly(_ flags: NSEvent.ModifierFlags) -> Bool {
event.modifierFlags.intersection([.command, .control, .option, .shift, .function]) == .command flags.intersection([.command, .control, .option, .shift, .function]) == .command
}
private var numberShortcutsEligible: Bool {
isKeyWindow && isVisible && attachedSheet == nil && canUseNumberShortcuts?() == true
}
func beginNumberHintSession() {
resetNumberHints()
numberShortcuts.reset()
}
func sampleNumberHints() {
reevaluateNumberHints(
modifierFlags: NSEvent.modifierFlags,
eligible: numberShortcutsEligible,
at: ProcessInfo.processInfo.systemUptime
)
}
/// The same sampling path accepts fixed flags, eligibility, and uptime for native smoke checks.
func reevaluateNumberHints(modifierFlags: NSEvent.ModifierFlags, eligible: Bool, at time: TimeInterval) {
let wasVisible = numberShortcuts.isVisible
let began = numberShortcuts.update(commandOnly: commandOnly(modifierFlags), eligible: eligible, at: time)
if !numberShortcuts.isHolding {
commandHoldTask?.cancel()
commandHoldTask = nil
} else if began {
commandHoldTask = Task { [weak self] in
do { try await Task.sleep(for: .seconds(1)) } catch { return }
self?.finishCommandHold(at: ProcessInfo.processInfo.systemUptime)
}
}
if wasVisible != numberShortcuts.isVisible { numberHintsChanged?() }
} }
func resetNumberHints() { func resetNumberHints() {
commandHoldTask?.cancel() commandHoldTask?.cancel()
commandHoldTask = nil commandHoldTask = nil
let wasVisible = numberShortcuts.isVisible let wasVisible = numberShortcuts.isVisible
numberShortcuts.reset() numberShortcuts.cancel()
if wasVisible { numberHintsChanged?() } if wasVisible { numberHintsChanged?() }
} }
func finishCommandHold(at time: TimeInterval) { func finishCommandHold(
guard isKeyWindow, isVisible, attachedSheet == nil, canUseNumberShortcuts?() == true else { at time: TimeInterval,
resetNumberHints() modifierFlags: NSEvent.ModifierFlags = NSEvent.modifierFlags,
return eligible: Bool? = nil
} ) {
if numberShortcuts.reveal(at: time) { commandHoldTask?.cancel()
commandHoldTask?.cancel() commandHoldTask = nil
commandHoldTask = nil let wasVisible = numberShortcuts.isVisible
numberHintsChanged?() _ = numberShortcuts.update(
} commandOnly: commandOnly(modifierFlags), eligible: eligible ?? numberShortcutsEligible, at: time
)
_ = numberShortcuts.reveal(at: time)
if wasVisible != numberShortcuts.isVisible { numberHintsChanged?() }
}
override func becomeKey() {
super.becomeKey()
sampleNumberHints()
}
override func beginSheet(_ sheetWindow: NSWindow, completionHandler handler: ((NSApplication.ModalResponse) -> Void)? = nil) {
resetNumberHints()
super.beginSheet(sheetWindow, completionHandler: handler)
} }
override func resignKey() { override func resignKey() {
@@ -837,18 +986,11 @@ final class LauncherPanel: NSPanel {
} }
override func sendEvent(_ event: NSEvent) { override func sendEvent(_ event: NSEvent) {
let eligible = isKeyWindow && isVisible && attachedSheet == nil && canUseNumberShortcuts?() == true let eligible = numberShortcutsEligible
if event.type == .flagsChanged { if event.type == .flagsChanged || (event.type == .keyUp && event.keyCode == 49) {
if !commandOnly(event) || !eligible { reevaluateNumberHints(modifierFlags: event.modifierFlags, eligible: eligible, at: event.timestamp)
resetNumberHints()
} else if numberShortcuts.update(commandOnly: true, eligible: true, at: event.timestamp) {
commandHoldTask = Task { [weak self] in
do { try await Task.sleep(for: .seconds(1)) } catch { return }
self?.finishCommandHold(at: ProcessInfo.processInfo.systemUptime)
}
}
} else if event.type == .keyDown { } else if event.type == .keyDown {
if eligible, commandOnly(event), if eligible, commandOnly(event.modifierFlags),
let index = numberShortcuts.index(for: event.charactersIgnoringModifiers ?? "") { let index = numberShortcuts.index(for: event.charactersIgnoringModifiers ?? "") {
resetNumberHints() resetNumberHints()
if !event.isARepeat { launchNumber?(index) } if !event.isARepeat { launchNumber?(index) }
+2 -1
View File
@@ -3,7 +3,7 @@ import CoreText
@MainActor @MainActor
final class ShortcutIndicator: NSStackView { final class ShortcutIndicator: NSStackView {
init(_ keys: String...) { init(_ keys: String..., backgroundColor: NSColor? = nil) {
super.init(frame: .zero) super.init(frame: .zero)
orientation = .horizontal orientation = .horizontal
alignment = .centerY alignment = .centerY
@@ -21,6 +21,7 @@ final class ShortcutIndicator: NSStackView {
badge.layer?.cornerRadius = 5 badge.layer?.cornerRadius = 5
badge.layer?.borderWidth = 1 badge.layer?.borderWidth = 1
badge.layer?.borderColor = SunsetPalette.secondaryText.withAlphaComponent(0.35).cgColor badge.layer?.borderColor = SunsetPalette.secondaryText.withAlphaComponent(0.35).cgColor
badge.layer?.backgroundColor = backgroundColor?.cgColor
badge.setAccessibilityElement(false) badge.setAccessibilityElement(false)
badge.translatesAutoresizingMaskIntoConstraints = false badge.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
@@ -3,13 +3,20 @@ import Foundation
/// Monotonic timestamps are supplied by the window; tests never wait on a real clock. /// Monotonic timestamps are supplied by the window; tests never wait on a real clock.
public struct CommandNumberShortcuts: Sendable { public struct CommandNumberShortcuts: Sendable {
private var heldSince: TimeInterval? private var heldSince: TimeInterval?
private var cancelledUntilRelease = false
public private(set) var isVisible = false public private(set) var isVisible = false
public var isHolding: Bool { heldSince != nil }
public init() {} public init() {}
/// Returns true only when a fresh, eligible Command-only hold begins. /// Samples an existing hold too, including when results first become eligible.
public mutating func update(commandOnly: Bool, eligible: Bool, at time: TimeInterval) -> Bool { public mutating func update(commandOnly: Bool, eligible: Bool, at time: TimeInterval) -> Bool {
guard commandOnly && eligible else { reset(); return false } guard commandOnly else { reset(); return false }
guard eligible && !cancelledUntilRelease else {
heldSince = nil
isVisible = false
return false
}
guard heldSince == nil else { return false } guard heldSince == nil else { return false }
heldSince = time heldSince = time
return true return true
@@ -27,8 +34,15 @@ public struct CommandNumberShortcuts: Sendable {
return Int(digit - 49) return Int(digit - 49)
} }
/// An action cancels this hold; modifier sampling must not restart it.
public mutating func cancel() {
reset()
cancelledUntilRelease = true
}
public mutating func reset() { public mutating func reset() {
heldSince = nil heldSince = nil
isVisible = false isVisible = false
cancelledUntilRelease = false
} }
} }
@@ -30,3 +30,37 @@ import Testing
let afterReset = shortcuts.reveal(at: 20) let afterReset = shortcuts.reveal(at: 20)
#expect(!afterReset) #expect(!afterReset)
} }
@Test(arguments: [false, true])
func commandHeldAtActivationArmsWithoutAModifierEvent(resultsInitiallyEligible: Bool) {
var shortcuts = CommandNumberShortcuts()
let activation = shortcuts.update(commandOnly: true, eligible: resultsInitiallyEligible, at: 10)
let afterDiscovery = shortcuts.update(commandOnly: true, eligible: true, at: 10.1)
let spaceReleased = shortcuts.update(commandOnly: true, eligible: true, at: 10.2)
#expect(activation == resultsInitiallyEligible)
#expect(afterDiscovery == !resultsInitiallyEligible && !spaceReleased)
let heldAt = resultsInitiallyEligible ? 10.0 : 10.1
let early = shortcuts.reveal(at: heldAt + 0.999)
let revealed = shortcuts.reveal(at: heldAt + 1)
#expect(!early && revealed)
}
@Test func commandShortcutCancellationSurvivesEligibilityAndSpaceReleaseSamples() {
var shortcuts = CommandNumberShortcuts()
_ = shortcuts.update(commandOnly: true, eligible: true, at: 10)
shortcuts.cancel()
_ = shortcuts.update(commandOnly: true, eligible: false, at: 10.2)
let afterDiscovery = shortcuts.update(commandOnly: true, eligible: true, at: 10.3)
let spaceReleased = shortcuts.update(commandOnly: true, eligible: true, at: 10.4)
let cancelled = shortcuts.reveal(at: 12)
#expect(!afterDiscovery && !spaceReleased && !cancelled && !shortcuts.isHolding)
_ = shortcuts.update(commandOnly: false, eligible: true, at: 12.1)
let freshHold = shortcuts.update(commandOnly: true, eligible: true, at: 13)
let revealed = shortcuts.reveal(at: 14)
#expect(freshHold && revealed)
shortcuts.cancel()
shortcuts.reset() // Reopening the launcher starts a new activation session.
let reopened = shortcuts.update(commandOnly: true, eligible: true, at: 15)
let reopenedVisible = shortcuts.reveal(at: 16)
#expect(reopened && reopenedVisible)
}