docs(workflow): add comprehensive git workflow standards #1

Merged
chaulmark merged 9 commits from develop into main 2025-01-06 17:06:02 +00:00
3 changed files with 211 additions and 43 deletions
Showing only changes of commit de60cfb2a1 - Show all commits

View file

@ -1,28 +1,107 @@
import SwiftUI import SwiftUI
import AppKit
class CustomNSView: NSView {
private static let invisibleCursor: NSCursor = {
let image = NSImage(size: NSSize(width: 1, height: 1))
image.lockFocus()
NSColor.clear.set()
NSRect(x: 0, y: 0, width: 1, height: 1).fill()
image.unlockFocus()
return NSCursor(image: image, hotSpot: .zero)
}()
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
private func setupView() {
wantsLayer = true
layer?.backgroundColor = NSColor.clear.cgColor
}
override func resetCursorRects() {
super.resetCursorRects()
addCursorRect(bounds, cursor: Self.invisibleCursor)
}
}
struct HiddenCursorView: NSViewRepresentable {
func makeNSView(context: Context) -> CustomNSView {
let view = CustomNSView()
view.wantsLayer = true
return view
}
func updateNSView(_ nsView: CustomNSView, context: Context) {
nsView.setFrameSize(nsView.frame.size)
}
}
struct CaptureStatusButton: View {
let isCapturing: Bool
let isBoardDetected: Bool
var body: some View {
HStack {
Circle()
.fill(statusColor)
.frame(width: 12, height: 12)
Text(statusText)
.foregroundColor(statusColor)
}
.padding()
.background(
RoundedRectangle(cornerRadius: 8)
.stroke(statusColor, lineWidth: 2)
)
}
private var statusColor: Color {
if isCapturing && isBoardDetected {
return .green
} else if isCapturing {
return .yellow
} else {
return .gray
}
}
private var statusText: String {
if isCapturing && isBoardDetected {
return "Capturing"
} else if isCapturing {
return "Waiting for board"
} else {
return "Not capturing"
}
}
}
struct ContentView: View { struct ContentView: View {
@StateObject private var viewModel = ScreenCaptureViewModel() @StateObject private var viewModel = ScreenCaptureViewModel()
@State private var showDebugOverlay = false
var body: some View { var body: some View {
VStack { VStack {
// Capture controls // Capture status indicator
HStack { CaptureStatusButton(
Button(action: { isCapturing: viewModel.isCapturing,
viewModel.startCapture() isBoardDetected: viewModel.isBoardDetected
}) { )
Text("Start Capture") .onAppear {
} // Start monitoring for chess boards when view appears
.disabled(viewModel.isCapturing) viewModel.startMonitoring()
}
Button(action: { .onDisappear {
viewModel.stopCapture() // Stop monitoring when view disappears
}) { viewModel.stopMonitoring()
Text("Stop Capture")
}
.disabled(!viewModel.isCapturing)
} }
.padding()
// Error display // Error display
if let error = viewModel.captureError { if let error = viewModel.captureError {
@ -55,7 +134,10 @@ struct ContentView: View {
.resizable() .resizable()
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(maxWidth: 400) .frame(maxWidth: 400)
.aspectRatio(contentMode: .fit) .overlay(
HiddenCursorView()
.allowsHitTesting(true)
)
} else { } else {
Text("No board detected") Text("No board detected")
.foregroundColor(.gray) .foregroundColor(.gray)

View file

@ -9,10 +9,13 @@ class ScreenCaptureViewModel: ObservableObject {
@Published var croppedBoardImage: NSImage? @Published var croppedBoardImage: NSImage?
@Published var captureError: CaptureError? @Published var captureError: CaptureError?
@Published var isCapturing = false @Published var isCapturing = false
@Published var isBoardDetected = false
@Published var isAutoCapturing = true // Default to auto-capture mode
private let screenCapture = ScreenCapture() private let screenCapture = ScreenCapture()
private let boardDetector = BoardDetector() private let boardDetector = BoardDetector()
private var captureTask: Task<Void, Never>? private var captureTask: Task<Void, Never>?
private var monitorTask: Task<Void, Never>?
// Share CIContext to avoid creating too many Metal command queues // Share CIContext to avoid creating too many Metal command queues
private static let shared = CIContext() private static let shared = CIContext()
private var context: CIContext { ScreenCaptureViewModel.shared } private var context: CIContext { ScreenCaptureViewModel.shared }
@ -22,6 +25,7 @@ class ScreenCaptureViewModel: ObservableObject {
enum CaptureError: LocalizedError { enum CaptureError: LocalizedError {
case chessWindowNotFound case chessWindowNotFound
case boardDetectionFailed case boardDetectionFailed
case noBoardDetected
var errorDescription: String? { var errorDescription: String? {
switch self { switch self {
@ -29,11 +33,58 @@ class ScreenCaptureViewModel: ObservableObject {
return "Chess window not found" return "Chess window not found"
case .boardDetectionFailed: case .boardDetectionFailed:
return "Failed to detect chess board" return "Failed to detect chess board"
case .noBoardDetected:
return "No chess board detected"
} }
} }
} }
func startMonitoring() {
guard monitorTask == nil else { return }
monitorTask = Task {
do {
// Start continuous monitoring
try await screenCapture.startCapture()
monitorLoop: while !Task.isCancelled {
do {
if let image = screenCapture.getCurrentImage(),
let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) {
// Check for board
let ciImage = CIImage(cgImage: cgImage)
if boardDetector.detectBoard(in: ciImage) != nil {
// Board detected, start capture if not already capturing
if !isCapturing {
startCapture()
}
}
}
try await Task.sleep(nanoseconds: 500_000_000) // 0.5 seconds
} catch is CancellationError {
break monitorLoop
} catch {
// Just log the error and continue monitoring
print("Monitor error: \(error)")
}
}
try? await screenCapture.stopCapture()
} catch {
print("Monitor setup error: \(error)")
}
}
}
func stopMonitoring() {
monitorTask?.cancel()
monitorTask = nil
stopCapture()
}
func startCapture() { func startCapture() {
guard !isCapturing else { return }
isCapturing = true isCapturing = true
captureError = nil captureError = nil
@ -54,6 +105,11 @@ class ScreenCaptureViewModel: ObservableObject {
// Just update the error state but continue capturing // Just update the error state but continue capturing
if !Task.isCancelled { if !Task.isCancelled {
handleCaptureError(error) handleCaptureError(error)
if isAutoCapturing && error as? CaptureError == .noBoardDetected {
// In auto-capture mode, stop capture but keep monitoring
stopCapture()
break captureLoop
}
} }
} }
} }
@ -75,6 +131,7 @@ class ScreenCaptureViewModel: ObservableObject {
captureTask = nil captureTask = nil
isCapturing = false isCapturing = false
captureError = nil captureError = nil
isBoardDetected = false
} }
private func processImage(_ image: NSImage) async throws { private func processImage(_ image: NSImage) async throws {
@ -86,16 +143,29 @@ class ScreenCaptureViewModel: ObservableObject {
let ciImage = CIImage(cgImage: cgImage) let ciImage = CIImage(cgImage: cgImage)
// Detect board // Detect board
guard let boardRect = boardDetector.detectBoard(in: ciImage) else { if let boardRect = boardDetector.detectBoard(in: ciImage) {
throw CaptureError.boardDetectionFailed // Board detected
isBoardDetected = true
self.detectedBoardRect = boardRect
// Crop board image
let croppedImage = ciImage.cropped(to: boardRect)
updateImages(ciImage: ciImage, croppedImage: croppedImage)
} else {
// No board detected
isBoardDetected = false
self.detectedBoardRect = nil
// Only update the full capture image
if let cgImage = context.createCGImage(ciImage, from: ciImage.extent) {
self.capturedImage = NSImage(cgImage: cgImage, size: .zero)
}
self.croppedBoardImage = nil
if isAutoCapturing {
throw CaptureError.noBoardDetected
}
} }
// Update detected rectangle
self.detectedBoardRect = boardRect
// Crop board image
let croppedImage = ciImage.cropped(to: boardRect)
updateImages(ciImage: ciImage, croppedImage: croppedImage)
} }
private func updateImages(ciImage: CIImage, croppedImage: CIImage) { private func updateImages(ciImage: CIImage, croppedImage: CIImage) {

View file

@ -7,9 +7,34 @@
- Error handling system properly managing states - Error handling system properly managing states
- Resource cleanup implemented - Resource cleanup implemented
- Metal resource management optimized - Metal resource management optimized
- Automatic board detection and capture implemented
- Visual capture status indicator added
- Continuous board monitoring system implemented
## Recent Changes ## Recent Changes
1. Resource Management Optimization: 1. Implemented Continuous Board Monitoring:
- Added separate monitoring and capture tasks:
* Monitor constantly checks for chess boards (0.5s interval)
* Capture processes frames when active (0.1s interval)
- Auto-capture behavior:
* Starts monitoring when app launches
* Automatically starts capture when board appears
* Stops capture (but keeps monitoring) when board disappears
* Resumes capture when new board is detected
- Fixed image conversion pipeline:
* Proper NSImage → CGImage → CIImage conversion
* Efficient resource management
* Clean error handling
2. Enhanced Status Indication:
- Visual status indicator shows capture state:
* Green: Actively capturing board
* Yellow: Waiting for board
* Gray: Not capturing
- Clear error messages for different states
- Automatic status updates based on board detection
3. Resource Management Optimization:
- Implemented shared CIContext pattern: - Implemented shared CIContext pattern:
* Prevents command queue exhaustion * Prevents command queue exhaustion
* Reduces Metal resource usage * Reduces Metal resource usage
@ -17,14 +42,14 @@
- Proper cleanup on task completion - Proper cleanup on task completion
- Efficient resource utilization - Efficient resource utilization
2. Improved Window Detection: 4. Improved Window Detection:
- Using SCShareableContent for window access - Using SCShareableContent for window access
- Precise window identification: - Precise window identification:
* Exact bundle ID matching (com.chess.iphone) * Exact bundle ID matching (com.chess.iphone)
* Window visibility verification (isOnScreen) * Window visibility verification (isOnScreen)
* Size validation (width > 100 && height > 100) * Size validation (width > 100 && height > 100)
3. Enhanced Capture System: 5. Enhanced Capture System:
- Continuous capture implementation: - Continuous capture implementation:
* Single persistent capture stream * Single persistent capture stream
* Smooth frame processing (no flickering) * Smooth frame processing (no flickering)
@ -33,19 +58,11 @@
- Clean task cancellation handling - Clean task cancellation handling
- Main thread safety for UI updates - Main thread safety for UI updates
4. UI Simplification: 6. Error Handling:
- Removed debug overlay functionality
- Cleaner, focused interface
- Essential controls only:
* Start/Stop capture
* Full capture view
* Board preview
5. Error Handling:
- Improved error resilience: - Improved error resilience:
* Continues capturing even if board detection fails * Continues monitoring even if capture stops
* Only stops on critical errors (e.g., window not found) * Only stops on critical errors
* Shows error state without interrupting capture * Shows error state without interrupting monitoring
- Clear error states - Clear error states
- Proper async/await usage - Proper async/await usage
- Task cancellation management - Task cancellation management
@ -68,7 +85,6 @@
5. Integrate Stockfish engine 5. Integrate Stockfish engine
## Known Issues ## Known Issues
- Board detection not yet implemented
- Need to handle different chess.com themes - Need to handle different chess.com themes
- Need to implement piece recognition - Need to implement piece recognition
- Position analysis pending implementation - Position analysis pending implementation