feat: add Command-number launch hints and Finder icon

This commit is contained in:
2026-10-13 22:08:14 +02:00
parent d3f9ec92d7
commit 77f62b8d41
7 changed files with 176 additions and 12 deletions
+3 -2
View File
@@ -33,6 +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.
- 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.
@@ -114,13 +115,13 @@ This is not full Raycast parity: calendar/date/time-zone calculations, design un
Select an application and open **Actions ⌘K**. **Open** is the first, default-selected action and uses the same launch path, successful-launch history tracking, dismissal, and error reporting as Return in the application list. Filtering resets selection to the first matching action; an empty result does nothing on Return. Up/Down or Control-K/Control-J navigates actions without changing the selected application. Select an application and open **Actions ⌘K**. **Open** is the first, default-selected action and uses the same launch path, successful-launch history tracking, dismissal, and error reporting as Return in the application list. Filtering resets selection to the first matching action; an empty result does nothing on Return. Up/Down or Control-K/Control-J navigates actions without changing the selected application.
- **Show in Finder** reveals the selected application bundle and dismisses the launcher without restoring the previous application's focus. - **Show in Finder** reveals the selected application bundle and dismisses the launcher without restoring the previous application's focus. Its icon is the native monochrome Finder glyph; macOS versions without that symbol use Finder's application icon.
- **Reset ranking** clears only the selected bundle's launch count and last-launch timestamp, including persisted history. Results reorder immediately while retaining the selected application; other copies and applications keep their history. Future launches start counting again. - **Reset ranking** clears only the selected bundle's launch count and last-launch timestamp, including persisted history. Results reorder immediately while retaining the selected application; other copies and applications keep their history. Future launches start counting again.
- **Quit application** sends a normal macOS quit request to running instances of that exact bundle, not another copy with the same name or bundle identifier. A non-running application is a no-op. The launcher dismisses after an accepted request; the application may still present save prompts or cancel quitting. Rejected requests show a native alert. Conjure never force-quits the application. - **Quit application** sends a normal macOS quit request to running instances of that exact bundle, not another copy with the same name or bundle identifier. A non-running application is a no-op. The launcher dismisses after an accepted request; the application may still present save prompts or cancel quitting. Rejected requests show a native alert. Conjure never force-quits the application.
The red **Uninstall application** action morphs the existing launcher into a file-review screen with checkboxes, paths, sizes, and a selected-item count; it does not create another window or review sheet. A native 180-millisecond resize and content crossfade runs in both directions, or switches immediately with Reduce Motion. The filter uses a centered, intrinsic-height text field without built-in search/cancel icons, beside an explicit back chevron. Filtering files by name or path does not change file selections. Up/Down or Control-K/Control-J highlights files; Return toggles the highlighted eligible file, including from the filter input, and Space also toggles when the list has focus. Back or Escape restores the original launcher frame, query, and selection. Temporary review geometry is not saved as the launcher position. Leaving is blocked during confirmation or an uninstall operation. Command-Return or the uninstall button opens a native final confirmation dialog; nothing is moved merely by entering review. The confirmation control uses the launcher's frosted rounded pill with separate Command and Return key badges. The launcher Actions control likewise shows separate Command and K badges matching its Open Return badge. The red **Uninstall application** action morphs the existing launcher into a file-review screen with checkboxes, paths, sizes, and a selected-item count; it does not create another window or review sheet. A native 180-millisecond resize and content crossfade runs in both directions, or switches immediately with Reduce Motion. The filter uses a centered, intrinsic-height text field without built-in search/cancel icons, beside an explicit back chevron. Filtering files by name or path does not change file selections. Up/Down or Control-K/Control-J highlights files; Return toggles the highlighted eligible file, including from the filter input, and Space also toggles when the list has focus. Back or Escape restores the original launcher frame, query, and selection. Temporary review geometry is not saved as the launcher position. Leaving is blocked during confirmation or an uninstall operation. Command-Return or the uninstall button opens a native final confirmation dialog; nothing is moved merely by entering review. The confirmation control uses the launcher's frosted rounded pill with separate Command and Return key badges. The launcher Actions control likewise shows separate Command and K badges matching its Open Return badge.
Keyboard file toggles update the existing checkbox without reloading or fading the list, preserving both the highlighted row and scroll position. The launcher's footer Open and Actions labels use 12-point medium text. All custom shortcut hints—in the launcher footer, uninstall footer, and actions selector—use `ShortcutIndicator` from `Sources/Conjure/ShortcutIndicator.swift` (for example, `ShortcutIndicator("⌘", "K")`). It owns 12-point medium key text, square 18×18-point containers, visible-glyph centering, border styling, and 2-point gaps between adjacent keys; callers only position or hide the whole indicator. Native menus and shortcut-recording controls retain their platform styling. Keyboard file toggles update the existing checkbox without reloading or fading the list, preserving both the highlighted row and scroll position. The launcher's footer Open and Actions labels use 12-point medium text. All custom shortcut hints—in the launcher footer, numbered application rows, uninstall footer, and actions selector—use `ShortcutIndicator` from `Sources/Conjure/ShortcutIndicator.swift` (for example, `ShortcutIndicator("⌘", "K")`). It owns 12-point medium key text, square 18×18-point containers, visible-glyph centering, border styling, and 2-point gaps between adjacent keys; callers position or hide the whole indicator, and numbered rows update their single key without rebuilding the cell. Native menus and shortcut-recording controls retain their platform styling.
The actions selector is an arrowless panel inside the launcher, aligned to the bottom and right edges of the footer action pill, replacing it visually while open rather than floating above it. It shares the launcher's frosted Sunset background, 38-point selection row height, 22×22-point icon boxes with 12-point icon-to-text spacing, 14-point action and search text, rounded selection styling, and Return key badge; selection width follows the panel. Application and command rows use the same sizing constants. SF Symbol optical alignment insets are removed so command icons render inside the same bounds as application icons. The application name appears at the top and borderless action search sits eight points from the bottom, without a divider. Native animations crossfade the panel and footer pill: 160 milliseconds to open and 120 milliseconds to close, with unchanged geometry. Reduce Motion disables the animation. Rapid reopening invalidates stale dismissal completions, and closing panels immediately stop intercepting input. Escape, Command-K, an outside click, or switching away closes it and restores the pill. Closing while Conjure remains active restores launcher search focus; switching away does not steal focus back. Return in action search executes the highlighted action: Open launches the application, while Uninstall opens its review. The actions selector is an arrowless panel inside the launcher, aligned to the bottom and right edges of the footer action pill, replacing it visually while open rather than floating above it. It shares the launcher's frosted Sunset background, 38-point selection row height, 22×22-point icon boxes with 12-point icon-to-text spacing, 14-point action and search text, rounded selection styling, and Return key badge; selection width follows the panel. Application and command rows use the same sizing constants. SF Symbol optical alignment insets are removed so command icons render inside the same bounds as application icons. The application name appears at the top and borderless action search sits eight points from the bottom, without a divider. Native animations crossfade the panel and footer pill: 160 milliseconds to open and 120 milliseconds to close, with unchanged geometry. Reduce Motion disables the animation. Rapid reopening invalidates stale dismissal completions, and closing panels immediately stop intercepting input. Escape, Command-K, an outside click, or switching away closes it and restores the pill. Closing while Conjure remains active restores launcher search focus; switching away does not steal focus back. Return in action search executes the highlighted action: Open launches the application, while Uninstall opens its review.
@@ -6,7 +6,7 @@ final class ApplicationActionsController: NSViewController, NSSearchFieldDelegat
private let search = ActionsSearchField() private let search = ActionsSearchField()
private let entries = [ private let entries = [
ActionEntry(title: "Open", symbol: "arrow.up.forward.app", color: SunsetPalette.text), ActionEntry(title: "Open", symbol: "arrow.up.forward.app", color: SunsetPalette.text),
ActionEntry(title: "Show in Finder", symbol: "folder", color: SunsetPalette.text), ActionEntry(title: "Show in Finder", symbol: "finder", color: SunsetPalette.text),
ActionEntry(title: "Reset ranking", symbol: "arrow.counterclockwise", color: SunsetPalette.text), ActionEntry(title: "Reset ranking", symbol: "arrow.counterclockwise", color: SunsetPalette.text),
ActionEntry(title: "Quit application", symbol: "stop.circle", color: SunsetPalette.text), ActionEntry(title: "Quit application", symbol: "stop.circle", color: SunsetPalette.text),
ActionEntry(title: "Uninstall application", symbol: "trash", color: .systemRed) ActionEntry(title: "Uninstall application", symbol: "trash", color: .systemRed)
@@ -269,6 +269,7 @@ private struct ActionEntry {
button.setAccessibilityLabel(title) button.setAccessibilityLabel(title)
let icon = ActionIconView() let icon = ActionIconView()
icon.image = NSImage(systemSymbolName: symbol, accessibilityDescription: nil) icon.image = NSImage(systemSymbolName: symbol, accessibilityDescription: nil)
?? (symbol == "finder" ? NSWorkspace.shared.icon(forFile: "/System/Library/CoreServices/Finder.app") : nil)
icon.contentTintColor = color icon.contentTintColor = color
icon.imageScaling = .scaleProportionallyDown icon.imageScaling = .scaleProportionallyDown
icon.setAccessibilityElement(false) icon.setAccessibilityElement(false)
+100 -2
View File
@@ -61,6 +61,16 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
else { self.moveSelection(by: offset) } else { self.moveSelection(by: offset) }
} }
window.dismiss = { [weak self] in self?.dismiss() } window.dismiss = { [weak self] in self?.dismiss() }
window.canUseNumberShortcuts = { [weak self] in
guard let self else { return false }
return !self.isLaunching && self.calculation == nil && !self.results.isEmpty
&& self.launcherFrameBeforeReview == nil && !self.actions.isShown
}
window.numberHintsChanged = { [weak self] in self?.updateNumberHints() }
window.launchNumber = { [weak self] index in
guard let self, self.results.indices.contains(index) else { return }
self.openApplication(self.results[index])
}
window.dragEnded = { [weak self] in window.dragEnded = { [weak self] in
guard let self else { return } guard let self else { return }
self.alignmentGuides?.end(snapping: self.window) self.alignmentGuides?.end(snapping: self.window)
@@ -171,6 +181,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
} }
func windowDidResignKey(_ notification: Notification) { func windowDidResignKey(_ notification: Notification) {
(window as? LauncherPanel)?.resetNumberHints()
guard !isLaunching, launcherFrameBeforeReview == nil, window?.attachedSheet == nil else { return } guard !isLaunching, launcherFrameBeforeReview == nil, window?.attachedSheet == nil else { return }
dismiss(restoringFocus: false) dismiss(restoringFocus: false)
} }
@@ -433,6 +444,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
detail: "\(answer.detail) · \(cacheStatus)" detail: "\(answer.detail) · \(cacheStatus)"
) )
} }
if calculation != nil { (window as? LauncherPanel)?.resetNumberHints() }
if calculation != nil { if calculation != nil {
results = [] results = []
} else if searchField.stringValue.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { } else if searchField.stringValue.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
@@ -535,6 +547,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
cell.imageView?.image = NSWorkspace.shared.icon(forFile: application.url.path) cell.imageView?.image = NSWorkspace.shared.icon(forFile: application.url.path)
cell.toolTip = application.url.path cell.toolTip = application.url.path
} }
configureNumberHint(cell, row: row)
return cell return cell
} }
@@ -546,6 +559,25 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
return view return view
} }
private func configureNumberHint(_ cell: ApplicationCell, row: Int) {
let visible = (window as? LauncherPanel)?.numberHintsVisible == true
&& calculation == nil && row < 9 && results.indices.contains(row)
cell.numberHint.isHidden = !visible
cell.kindLabel.isHidden = visible
if visible { cell.numberHint.setSingleKey(String(row + 1)) }
cell.setAccessibilityHelp(visible ? "Command-\(row + 1) opens this application" : nil)
}
private func updateNumberHints() {
let rows = tableView.rows(in: tableView.visibleRect)
guard rows.length > 0 else { return }
for row in rows.location..<(rows.location + rows.length) {
if let cell = tableView.view(atColumn: 0, row: row, makeIfNecessary: false) as? ApplicationCell {
configureNumberHint(cell, row: row)
}
}
}
private func makeCell(identifier: NSUserInterfaceItemIdentifier) -> ApplicationCell { private func makeCell(identifier: NSUserInterfaceItemIdentifier) -> ApplicationCell {
let cell = ApplicationCell() let cell = ApplicationCell()
cell.identifier = identifier cell.identifier = identifier
@@ -573,7 +605,8 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
kind.setAccessibilityElement(false) kind.setAccessibilityElement(false)
cell.imageView = icon cell.imageView = icon
cell.textField = label cell.textField = label
for view in [icon, title, kind] { cell.numberHint.isHidden = true
for view in [icon, title, kind, cell.numberHint] {
view.translatesAutoresizingMaskIntoConstraints = false view.translatesAutoresizingMaskIntoConstraints = false
cell.addSubview(view) cell.addSubview(view)
} }
@@ -587,12 +620,15 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
title.trailingAnchor.constraint(lessThanOrEqualTo: kind.leadingAnchor, constant: -12), title.trailingAnchor.constraint(lessThanOrEqualTo: kind.leadingAnchor, constant: -12),
kind.centerYAnchor.constraint(equalTo: cell.centerYAnchor), kind.centerYAnchor.constraint(equalTo: cell.centerYAnchor),
kind.trailingAnchor.constraint(equalTo: cell.trailingAnchor, constant: -8), 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
} }
@objc private func showActions() { @objc private func showActions() {
guard !isLaunching, launcherFrameBeforeReview == nil, window?.attachedSheet == nil, results.indices.contains(tableView.selectedRow) else { return } guard !isLaunching, launcherFrameBeforeReview == nil, window?.attachedSheet == nil, results.indices.contains(tableView.selectedRow) else { return }
(window as? LauncherPanel)?.resetNumberHints()
let application = results[tableView.selectedRow] let application = results[tableView.selectedRow]
actions.open = { [weak self] in self?.openApplication(application) } actions.open = { [weak self] in self?.openApplication(application) }
actions.showInFinder = { [weak self] in actions.showInFinder = { [weak self] in
@@ -635,6 +671,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
review.view.bottomAnchor.constraint(equalTo: content.bottomAnchor) review.view.bottomAnchor.constraint(equalTo: content.bottomAnchor)
]) ])
for view in launcherViews { view.setAccessibilityHidden(true) } for view in launcherViews { view.setAccessibilityHidden(true) }
(window as? LauncherPanel)?.resetNumberHints()
openButton.keyEquivalent = "" openButton.keyEquivalent = ""
updateOpenButton() updateOpenButton()
var frame = window.frame var frame = window.frame
@@ -699,6 +736,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
private func openApplication(_ application: Application) { private func openApplication(_ application: Application) {
guard !isLaunching else { return } guard !isLaunching else { return }
(window as? LauncherPanel)?.resetNumberHints()
isLaunching = true isLaunching = true
updateOpenButton() updateOpenButton()
Task { Task {
@@ -723,6 +761,7 @@ final class LauncherWindowController: NSWindowController, NSWindowDelegate, NSSe
private final class ApplicationCell: NSTableCellView { private final class ApplicationCell: NSTableCellView {
let locationLabel = NSTextField(labelWithString: "") let locationLabel = NSTextField(labelWithString: "")
let kindLabel = NSTextField(labelWithString: "Application") let kindLabel = NSTextField(labelWithString: "Application")
let numberHint = ShortcutIndicator("1")
} }
@MainActor @MainActor
@@ -750,14 +789,73 @@ private final class ApplicationTableView: NSTableView {
} }
@MainActor @MainActor
private final class LauncherPanel: NSPanel { final class LauncherPanel: NSPanel {
override var canBecomeKey: Bool { true } override var canBecomeKey: Bool { true }
override var canBecomeMain: Bool { true } override var canBecomeMain: Bool { true }
var navigateSelection: ((Int) -> Void)? var navigateSelection: ((Int) -> Void)?
var dismiss: (() -> Void)? var dismiss: (() -> Void)?
var dragEnded: (() -> Void)? var dragEnded: (() -> Void)?
var canUseNumberShortcuts: (() -> Bool)?
var numberHintsChanged: (() -> Void)?
var launchNumber: ((Int) -> Void)?
private var numberShortcuts = CommandNumberShortcuts()
private var commandHoldTask: Task<Void, Never>?
var numberHintsVisible: Bool { numberShortcuts.isVisible }
private func commandOnly(_ event: NSEvent) -> Bool {
event.modifierFlags.intersection([.command, .control, .option, .shift, .function]) == .command
}
func resetNumberHints() {
commandHoldTask?.cancel()
commandHoldTask = nil
let wasVisible = numberShortcuts.isVisible
numberShortcuts.reset()
if wasVisible { numberHintsChanged?() }
}
func finishCommandHold(at time: TimeInterval) {
guard isKeyWindow, isVisible, attachedSheet == nil, canUseNumberShortcuts?() == true else {
resetNumberHints()
return
}
if numberShortcuts.reveal(at: time) {
commandHoldTask?.cancel()
commandHoldTask = nil
numberHintsChanged?()
}
}
override func resignKey() {
resetNumberHints()
super.resignKey()
}
override func orderOut(_ sender: Any?) {
resetNumberHints()
super.orderOut(sender)
}
override func sendEvent(_ event: NSEvent) { override func sendEvent(_ event: NSEvent) {
let eligible = isKeyWindow && isVisible && attachedSheet == nil && canUseNumberShortcuts?() == true
if event.type == .flagsChanged {
if !commandOnly(event) || !eligible {
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 {
if eligible, commandOnly(event),
let index = numberShortcuts.index(for: event.charactersIgnoringModifiers ?? "") {
resetNumberHints()
if !event.isARepeat { launchNumber?(index) }
return
}
resetNumberHints()
}
if event.type == .keyDown, attachedSheet == nil, if event.type == .keyDown, attachedSheet == nil,
event.modifierFlags.intersection([.control, .command, .option, .shift]) == .control { event.modifierFlags.intersection([.control, .command, .option, .shift]) == .control {
switch event.charactersIgnoringModifiers?.lowercased() { switch event.charactersIgnoringModifiers?.lowercased() {
+5
View File
@@ -31,6 +31,11 @@ final class ShortcutIndicator: NSStackView {
} }
} }
func setSingleKey(_ key: String) {
precondition(arrangedSubviews.count == 1)
(arrangedSubviews[0] as? NSTextField)?.stringValue = key
}
required init?(coder: NSCoder) { fatalError("Conjure creates shortcut indicators programmatically") } required init?(coder: NSCoder) { fatalError("Conjure creates shortcut indicators programmatically") }
} }
@@ -0,0 +1,34 @@
import Foundation
/// Monotonic timestamps are supplied by the window; tests never wait on a real clock.
public struct CommandNumberShortcuts: Sendable {
private var heldSince: TimeInterval?
public private(set) var isVisible = false
public init() {}
/// Returns true only when a fresh, eligible Command-only hold begins.
public mutating func update(commandOnly: Bool, eligible: Bool, at time: TimeInterval) -> Bool {
guard commandOnly && eligible else { reset(); return false }
guard heldSince == nil else { return false }
heldSince = time
return true
}
public mutating func reveal(at time: TimeInterval) -> Bool {
guard let heldSince, time - heldSince >= 1 else { return false }
isVisible = true
return true
}
public func index(for key: String) -> Int? {
guard isVisible, key.utf8.count == 1, let digit = key.utf8.first,
(49...57).contains(digit) else { return nil }
return Int(digit - 49)
}
public mutating func reset() {
heldSince = nil
isVisible = false
}
}
-7
View File
@@ -1,7 +0,0 @@
- [x] make the actions indicator font size smaller.
- [x] decrease the distance between the icons and their text by 1/3.
- [x] add more actions to the actions selector:
- show in finder
- reset ranking
- quit application
- [x] add unit conversion and calculator behavior like raylib has.
@@ -0,0 +1,32 @@
import ConjureKit
import Testing
@Test func commandNumbersRequireAnUninterruptedEligibleOneSecondHold() {
var shortcuts = CommandNumberShortcuts()
let started = shortcuts.update(commandOnly: true, eligible: true, at: 10)
let repeated = shortcuts.update(commandOnly: true, eligible: true, at: 10.5)
let early = shortcuts.reveal(at: 10.999)
#expect(started && !repeated && !early)
#expect(shortcuts.index(for: "1") == nil)
let revealed = shortcuts.reveal(at: 11)
#expect(revealed)
#expect(shortcuts.index(for: "1") == 0)
#expect(shortcuts.index(for: "9") == 8)
for key in ["0", "10", "k", "", "١"] { #expect(shortcuts.index(for: key) == nil) }
_ = shortcuts.update(commandOnly: false, eligible: true, at: 11.1)
#expect(!shortcuts.isVisible)
let afterRelease = shortcuts.reveal(at: 12)
#expect(!afterRelease)
_ = shortcuts.update(commandOnly: true, eligible: true, at: 13)
_ = shortcuts.update(commandOnly: true, eligible: false, at: 13.5)
let blocked = shortcuts.reveal(at: 14)
#expect(!blocked)
_ = shortcuts.update(commandOnly: true, eligible: true, at: 15)
let restartedEarly = shortcuts.reveal(at: 15.5)
let restarted = shortcuts.reveal(at: 16)
#expect(!restartedEarly && restarted)
shortcuts.reset()
#expect(shortcuts.index(for: "2") == nil)
let afterReset = shortcuts.reveal(at: 20)
#expect(!afterReset)
}