Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 57 additions & 47 deletions .agent-os/specs/2025-07-22-phase1-completion/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,50 +7,60 @@ These are the tasks to be completed for the spec detailed in @.agent-os/specs/20

## Tasks

- [ ] 1. **Implement Pause/Resume UI Controls**
- [ ] 1.1 Write tests for pause/resume button components and state management
- [ ] 1.2 Add pause/resume buttons to main automation control panel in ClickItViewModel
- [ ] 1.3 Integrate pause/resume functionality with ElapsedTimeManager and ClickCoordinator
- [ ] 1.4 Update visual feedback overlay to reflect pause/resume states
- [ ] 1.5 Ensure session statistics preservation during pause state
- [ ] 1.6 Verify all tests pass and UI responds correctly

- [ ] 2. **Build Enhanced Preset Management System**
- [ ] 2.1 Write tests for PresetManager and PresetConfiguration data structures
- [ ] 2.2 Create PresetManager class with UserDefaults integration for save/load functionality
- [ ] 2.3 Design and implement preset management UI components (save, load, delete, custom naming)
- [ ] 2.4 Add preset validation logic to ensure saved configurations are valid
- [ ] 2.5 Integrate preset system with ClickItViewModel and all configuration properties
- [ ] 2.6 Implement preset selection dropdown and management interface
- [ ] 2.7 Add preset export/import capability for backup and sharing
- [ ] 2.8 Verify all tests pass and preset system works end-to-end

- [ ] 3. **Develop Comprehensive Error Recovery System**
- [ ] 3.1 Write tests for ErrorRecoveryManager and error detection mechanisms
- [ ] 3.2 Create ErrorRecoveryManager to monitor system state and handle failures
- [ ] 3.3 Implement automatic retry logic for click failures and permission issues
- [ ] 3.4 Add error notification system with clear user feedback and recovery status
- [ ] 3.5 Integrate error recovery hooks into ClickCoordinator and automation loops
- [ ] 3.6 Implement graceful degradation strategies when recovery fails
- [ ] 3.7 Add system health monitoring for permissions and resource availability
- [ ] 3.8 Verify all tests pass and error recovery works under failure conditions

- [ ] 4. **Optimize Performance for Sub-10ms Timing**
- [ ] 4.1 Write performance benchmark tests for timing accuracy and resource usage
- [ ] 4.2 Implement HighPrecisionTimer system with optimized timing loops
- [ ] 4.3 Profile and optimize memory usage to meet <50MB RAM target
- [ ] 4.4 Optimize CPU usage to achieve <5% idle target with efficient background processing
- [ ] 4.5 Add real-time performance monitoring and metrics collection
- [ ] 4.6 Implement automated performance validation and regression testing
- [ ] 4.7 Create performance dashboard for user visibility into timing accuracy
- [ ] 4.8 Verify all performance targets met and benchmarks pass consistently

- [ ] 5. **Implement Advanced CPS Randomization**
- [ ] 5.1 Write tests for CPSRandomizer and timing pattern generation
- [ ] 5.2 Create CPSRandomizer with configurable variance and distribution patterns
- [ ] 5.3 Add UI controls for randomization settings and pattern selection
- [ ] 5.4 Implement statistical distributions (normal, uniform) for natural timing variation
- [ ] 5.5 Integrate randomization with AutomationConfiguration and clicking loops
- [ ] 5.6 Add validation to ensure randomization doesn't break timing requirements
- [ ] 5.7 Implement anti-detection patterns to avoid automation signature detection
- [ ] 5.8 Verify all tests pass and randomization produces human-like patterns
- [x] 1. **Implement Pause/Resume UI Controls**
- [x] 1.1 Write tests for pause/resume button components and state management
- [x] 1.2 Add pause/resume buttons to main automation control panel in ClickItViewModel
- [x] 1.3 Integrate pause/resume functionality with ElapsedTimeManager and ClickCoordinator
- [x] 1.4 Update visual feedback overlay to reflect pause/resume states
- [x] 1.5 Ensure session statistics preservation during pause state
- [x] 1.6 Verify all tests pass and UI responds correctly

- [ ] 2. **🚨 EMERGENCY: Enhance Emergency Stop System** (HIGH PRIORITY)
- [ ] 2.1 Write tests for enhanced emergency stop functionality
- [ ] 2.2 Implement multiple emergency stop key options (ESC, F1, Cmd+Period, Space)
- [ ] 2.3 Add configurable emergency stop key selection in settings
- [ ] 2.4 Implement immediate stop with <50ms response time guarantee
- [ ] 2.5 Add visual confirmation of emergency stop activation
- [ ] 2.6 Ensure emergency stop works even when app is in background
- [ ] 2.7 Add emergency stop status to automation panel and overlay
- [ ] 2.8 Verify emergency stop reliability across all automation states

- [ ] 3. **Build Enhanced Preset Management System**
- [ ] 3.1 Write tests for PresetManager and PresetConfiguration data structures
- [ ] 3.2 Create PresetManager class with UserDefaults integration for save/load functionality
- [ ] 3.3 Design and implement preset management UI components (save, load, delete, custom naming)
- [ ] 3.4 Add preset validation logic to ensure saved configurations are valid
- [ ] 3.5 Integrate preset system with ClickItViewModel and all configuration properties
- [ ] 3.6 Implement preset selection dropdown and management interface
- [ ] 3.7 Add preset export/import capability for backup and sharing
- [ ] 3.8 Verify all tests pass and preset system works end-to-end

- [ ] 4. **Develop Comprehensive Error Recovery System**
- [ ] 4.1 Write tests for ErrorRecoveryManager and error detection mechanisms
- [ ] 4.2 Create ErrorRecoveryManager to monitor system state and handle failures
- [ ] 4.3 Implement automatic retry logic for click failures and permission issues
- [ ] 4.4 Add error notification system with clear user feedback and recovery status
- [ ] 4.5 Integrate error recovery hooks into ClickCoordinator and automation loops
- [ ] 4.6 Implement graceful degradation strategies when recovery fails
- [ ] 4.7 Add system health monitoring for permissions and resource availability
- [ ] 4.8 Verify all tests pass and error recovery works under failure conditions

- [ ] 5. **Optimize Performance for Sub-10ms Timing**
- [ ] 5.1 Write performance benchmark tests for timing accuracy and resource usage
- [ ] 5.2 Implement HighPrecisionTimer system with optimized timing loops
- [ ] 5.3 Profile and optimize memory usage to meet <50MB RAM target
- [ ] 5.4 Optimize CPU usage to achieve <5% idle target with efficient background processing
- [ ] 5.5 Add real-time performance monitoring and metrics collection
- [ ] 5.6 Implement automated performance validation and regression testing
- [ ] 5.7 Create performance dashboard for user visibility into timing accuracy
- [ ] 5.8 Verify all performance targets met and benchmarks pass consistently

- [ ] 6. **Implement Advanced CPS Randomization**
- [ ] 6.1 Write tests for CPSRandomizer and timing pattern generation
- [ ] 6.2 Create CPSRandomizer with configurable variance and distribution patterns
- [ ] 6.3 Add UI controls for randomization settings and pattern selection
- [ ] 6.4 Implement statistical distributions (normal, uniform) for natural timing variation
- [ ] 6.5 Integrate randomization with AutomationConfiguration and clicking loops
- [ ] 6.6 Add validation to ensure randomization doesn't break timing requirements
- [ ] 6.7 Implement anti-detection patterns to avoid automation signature detection
- [ ] 6.8 Verify all tests pass and randomization produces human-like patterns
81 changes: 62 additions & 19 deletions Sources/ClickIt/UI/Components/StatusHeaderCard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,28 +66,71 @@ struct StatusHeaderCard: View {
)
}

// Primary Action Button
Button(action: {
if viewModel.isRunning {
viewModel.stopAutomation()
} else {
viewModel.startAutomation()
}
}) {
HStack(spacing: 8) {
Image(systemName: viewModel.isRunning ? "stop.fill" : "play.fill")
.font(.system(size: 16, weight: .medium))
// Control Buttons
if viewModel.isRunning || viewModel.isPaused {
// Running/Paused state: Show Pause/Resume and Stop buttons
HStack(spacing: 12) {
// Pause/Resume Button
Button(action: {
if viewModel.canPause {
viewModel.pauseAutomation()
} else if viewModel.canResume {
viewModel.resumeAutomation()
}
}) {
HStack(spacing: 6) {
Image(systemName: viewModel.isPaused ? "play.fill" : "pause.fill")
.font(.system(size: 14, weight: .medium))

Text(viewModel.isPaused ? "Resume" : "Pause")
.font(.subheadline)
.fontWeight(.medium)
}
.frame(maxWidth: .infinity)
.frame(height: 36)
}
.buttonStyle(.bordered)
.disabled(!viewModel.canPause && !viewModel.canResume)
.tint(viewModel.isPaused ? .green : .orange)

Text(viewModel.isRunning ? "Stop Automation" : "Start Automation")
.font(.headline)
.fontWeight(.medium)
// Stop Button
Button(action: {
viewModel.stopAutomation()
}) {
HStack(spacing: 6) {
Image(systemName: "stop.fill")
.font(.system(size: 14, weight: .medium))

Text("Stop")
.font(.subheadline)
.fontWeight(.medium)
}
.frame(maxWidth: .infinity)
.frame(height: 36)
}
.buttonStyle(.borderedProminent)
.tint(.red)
}
} else {
// Ready state: Show Start button
Button(action: {
viewModel.startAutomation()
}) {
HStack(spacing: 8) {
Image(systemName: "play.fill")
.font(.system(size: 16, weight: .medium))

Text("Start Automation")
.font(.headline)
.fontWeight(.medium)
}
.frame(maxWidth: .infinity)
.frame(height: 44)
}
.frame(maxWidth: .infinity)
.frame(height: 44)
.buttonStyle(.borderedProminent)
.disabled(!viewModel.canStartAutomation)
.tint(.green)
}
.buttonStyle(.borderedProminent)
.disabled(!viewModel.canStartAutomation && !viewModel.isRunning)
.tint(viewModel.isRunning ? .red : .green)
}
.padding(16)
.background(Color(NSColor.controlBackgroundColor))
Expand Down
91 changes: 90 additions & 1 deletion Sources/ClickIt/UI/ViewModels/ClickItViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class ClickItViewModel: ObservableObject {
// MARK: - Published Properties
@Published var targetPoint: CGPoint?
@Published var isRunning = false
@Published var isPaused = false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Having both isRunning and isPaused as separate @Published properties can lead to inconsistent states. Consider using the appStatus enum as the single source of truth and derive isRunning and isPaused as computed properties1.

Style Guide References

Footnotes

  1. Using a single source of truth for state management improves consistency and reduces potential errors. (link)

@Published var appStatus: AppStatus = .ready

// Configuration Properties
Expand Down Expand Up @@ -69,6 +70,14 @@ class ClickItViewModel: ObservableObject {
return total >= 1 && total <= 3600 // 1 second to 60 minutes
}

var canPause: Bool {
isRunning && !isPaused
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Given the state management, the !isPaused check is redundant since isRunning implies !isPaused. Consider simplifying to just isRunning.

    var canPause: Bool {
        isRunning
    }

}

var canResume: Bool {
isPaused && !isRunning
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to canPause, the !isRunning check is redundant. When isPaused is true, isRunning is always false. Simplify to isPaused.

    var canResume: Bool {
        isPaused
    }

}

// MARK: - Dependencies
private let clickCoordinator = ClickCoordinator.shared

Expand Down Expand Up @@ -151,9 +160,83 @@ class ClickItViewModel: ObservableObject {
clickCoordinator.stopAutomation()
cancelTimer() // Also cancel any active timer
isRunning = false
isPaused = false
appStatus = .ready
}

func pauseAutomation() {
guard isRunning && !isPaused else { return }

clickCoordinator.stopAutomation()
ElapsedTimeManager.shared.pauseTracking()

// Update visual feedback to show paused state (dimmed)
if showVisualFeedback, let point = targetPoint {
VisualFeedbackOverlay.shared.updateOverlay(at: point, isActive: false)
}

isRunning = false
isPaused = true
appStatus = .paused
}

func resumeAutomation() {
guard isPaused && !isRunning else { return }

// Resume elapsed time tracking
ElapsedTimeManager.shared.resumeTracking()

// Restart automation with current configuration
guard let point = targetPoint else { return }

let config = AutomationConfiguration(
location: point,
clickType: clickType,
clickInterval: Double(totalMilliseconds) / 1000.0,
targetApplication: nil,
maxClicks: durationMode == .clickCount ? maxClicks : nil,
maxDuration: durationMode == .timeLimit ? durationSeconds : nil,
stopOnError: stopOnError,
randomizeLocation: randomizeLocation,
locationVariance: CGFloat(randomizeLocation ? locationVariance : 0),
showVisualFeedback: showVisualFeedback,
useDynamicMouseTracking: false
)
Comment on lines +192 to +204
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This AutomationConfiguration block is duplicated in startAutomationForTesting(). Extract this logic into a private helper method to avoid code duplication and improve maintainability1.

Style Guide References

Footnotes

  1. Extracting duplicated code into a helper method improves maintainability and reduces the risk of errors. (link)


clickCoordinator.startAutomation(with: config)
isRunning = true
isPaused = false
appStatus = .running

// Update visual feedback to show active state
if showVisualFeedback {
VisualFeedbackOverlay.shared.updateOverlay(at: point, isActive: true)
}
}

// MARK: - Testing Methods
func startAutomationForTesting() {
guard let point = targetPoint else { return }

let config = AutomationConfiguration(
location: point,
clickType: clickType,
clickInterval: Double(totalMilliseconds) / 1000.0,
targetApplication: nil,
maxClicks: durationMode == .clickCount ? maxClicks : nil,
maxDuration: durationMode == .timeLimit ? durationSeconds : nil,
stopOnError: stopOnError,
randomizeLocation: randomizeLocation,
locationVariance: CGFloat(randomizeLocation ? locationVariance : 0),
showVisualFeedback: false, // Disable visual feedback for tests
useDynamicMouseTracking: false
)

clickCoordinator.startAutomation(with: config)
isRunning = true
appStatus = .running
}

func resetConfiguration() {
intervalHours = 0
intervalMinutes = 0
Expand Down Expand Up @@ -189,9 +272,10 @@ class ClickItViewModel: ObservableObject {
guard let self = self else { return }

// Sync ViewModel state with ClickCoordinator state
if !isActive && self.isRunning {
if !isActive && (self.isRunning || self.isPaused) {
print("ClickItViewModel: Automation stopped externally (e.g., DELETE key), updating UI state")
self.isRunning = false
self.isPaused = false
self.appStatus = .ready
// Also cancel any active timer when automation stops
self.cancelTimer()
Expand Down Expand Up @@ -349,6 +433,7 @@ enum TimerMode {
enum AppStatus {
case ready
case running
case paused
case error(String)

var displayText: String {
Expand All @@ -357,6 +442,8 @@ enum AppStatus {
return "Ready"
case .running:
return "Running"
case .paused:
return "Paused"
case .error(let message):
return "Error: \(message)"
}
Expand All @@ -368,6 +455,8 @@ enum AppStatus {
return .green
case .running:
return .blue
case .paused:
return .orange
case .error:
return .red
}
Expand Down
Loading
Loading