TestBuddy (main.py)
├── Config Management (config.py)
│ └── INI File ← Settings
├── History Management (history.py)
│ └── JSON File ← OCR Sessions
└── PyQt6 GUI (main.py)
├── OCRWorker Thread (non-blocking)
│ └── Tesseract OCR
└── UI Components
├── Screenshot Control
├── Text Editor
├── Activity Log
└── Buttons & Shortcuts
- Config dataclass: All settings with type hints
- ConfigManager: Load/save INI files
- Features:
- Type-safe access to settings
- Default values
- Error handling for missing/invalid values
- INI persistence
- HistoryEntry dataclass: Single OCR result
- HistoryManager: JSON-based storage
- Features:
- Add, search, delete entries
- Full-text search
- Statistics generation
- Auto-save
- OCRWorker: QThread for non-blocking OCR
- SnapOCRApp: Main PyQt6 window
- Features:
- Screenshot integration
- Clipboard polling
- UI rendering
- Event handling
- Logging
All functions are fully type-hinted:
def process_image(self, image: Image.Image, language: str) -> str:
"""Process image and extract text."""
pass
def add_entry(self, text: str, language: str = "eng", tags: Optional[List[str]] = None) -> None:
"""Add OCR result to history."""
passBenefits:
- IDE autocomplete ✓
- Static type checking with mypy
- Self-documenting code
- Early error detection
python main.pypython -m py_compile main.py config.py history.pypip install mypy
mypy main.py config.py history.pytype testbuddy_debug.logAdd UI dropdown for language selection:
# In _build_ui():
self.language_combo = QComboBox(self)
self.language_combo.addItems(["English (eng)", "French (fra)", "German (deu)", ...])
self.language_combo.currentTextChanged.connect(self.on_language_changed)
def on_language_changed(self, text: str) -> None:
code = text.split("(")[1].rstrip(")")
config.ocr_language = code
config_manager.save()Display captured screenshot before OCR:
# In history.py or new preview.py:
@dataclass
class ImagePreview:
image: Image.Image
timestamp: str
dimensions: tuple
file_size: int
# In main.py:
def show_image_preview(self, img: Image.Image) -> None:
preview_dialog = QDialog(self)
layout = QVBoxLayout()
label = QLabel()
label.setPixmap(QPixmap.fromImage(img))
layout.addWidget(label)
preview_dialog.exec()Use QTextEdit's built-in undo/redo:
# Already supported in QTextEdit!
# Just enable:
self.text_area = QTextEdit(left)
self.text_area.setReadOnly(False)
self.text_area.setUndoRedoEnabled(True)
# Add buttons:
self.btn_undo = QPushButton("Undo", left)
self.btn_undo.setShortcut("Ctrl+Z")
self.btn_undo.clicked.connect(self.text_area.undo)
self.btn_redo = QPushButton("Redo", left)
self.btn_redo.setShortcut("Ctrl+Y")
self.btn_redo.clicked.connect(self.text_area.redo)Create theme system:
# New file: themes.py
class Theme:
name: str
background: str
text: str
button: str
LIGHT = Theme(name="light", background="#ffffff", text="#000000", button="#007bff")
DARK = Theme(name="dark", background="#1e1e1e", text="#ffffff", button="#0d6efd")
def apply_theme(app: QApplication, theme: Theme) -> None:
stylesheet = f"""
QMainWindow {{ background-color: {theme.background}; }}
QTextEdit {{ background-color: {theme.background}; color: {theme.text}; }}
"""
app.setStyleSheet(stylesheet)# ✓ Good
def function(param: str, count: int = 5) -> Optional[str]:
pass
# ✗ Bad
def function(param, count=5):
passclass MyClass:
"""Short description.
Longer explanation of what this class does and how to use it.
"""
def public_method(self) -> None:
"""Do something important."""
pass# ✓ Good - Specific exceptions
try:
result = pytesseract.image_to_string(img)
except FileNotFoundError:
self._log("ERROR", "Tesseract not found")
except Exception as e:
self._log("ERROR", f"OCR failed: {e}")
# ✗ Bad - Bare except
try:
result = pytesseract.image_to_string(img)
except:
pass# ✓ Good - Structured logging
self._log("INFO", "OCR started", f"language={config.ocr_language}")
self._log("ERROR", "Export failed", str(exception))
# ✗ Bad - Vague logging
print("Started")
print("Error!")- Define config options (if needed)
# In config.py, add to Config dataclass:
new_feature_enabled: bool = True- Create feature module (if complex)
# new_feature.py
class NewFeature:
def __init__(self, config: Config):
self.config = config
def process(self, data: str) -> str:
"""Process data."""
return data- Integrate into main.py
from new_feature import NewFeature
# In SnapOCRApp.__init__():
self.feature = NewFeature(config)
# In action method:
self.feature.process(self._last_text)- Add UI controls (if user-facing)
# In _build_ui():
self.btn_feature = QPushButton("New Feature", self)
self.btn_feature.clicked.connect(self.on_feature_clicked)
def on_feature_clicked(self) -> None:
result = self.feature.process(self._last_text)
self._log("INFO", "Feature executed")- Test & document
# Write docstrings
# Update README.md
# Add config explanation to CONFIGURATION.mdclass Plugin(ABC):
@abstractmethod
def process(self, text: str) -> str:
pass
class SpellChecker(Plugin):
def process(self, text: str) -> str:
# Correct spelling
return corrected_textclass SettingsDialog(QDialog):
def __init__(self, config_manager: ConfigManager):
super().__init__()
# Generate UI from Config dataclass
# Save changes back# Currently: Simple text search
# Future:
# - Date range filtering
# - Tag-based filtering
# - Duplicate detection
# - Export/import sessions- Test with simple text images
- Test with complex layouts (tables, columns)
- Test with multiple languages
- Test history save/load
- Test settings persistence
- Check log file is created
- Verify no crashes on edge cases
- Test keyboard shortcuts
- Test with non-admin user
- Check memory usage (< 500MB)
- Reduce polling interval for faster response (tradeoff: CPU)
- Cache recent OCR results to avoid re-processing
- Lazy-load language data (only load when needed)
- Use threading for any blocking operation (already done for OCR)
- Limit history size to prevent JSON bloat
| Problem | Cause | Solution |
|---|---|---|
| Slow OCR | Complex image | Crop/simplify before OCR |
| Memory leak | History grows | Implement size limits |
| UI freezes | Blocking operation | Move to QThread |
| Lost settings | File not saved | Call config_manager.save() |
| Empty history | JSON corrupted | Delete and restart |
- PyQt6 Docs: https://www.riverbankcomputing.com/static/Docs/PyQt6/
- Tesseract OCR: https://github.com/UB-Mannheim/tesseract/wiki
- Python Type Hints: https://docs.python.org/3/library/typing.html
- Python Logging: https://docs.python.org/3/howto/logging.html
Ready to contribute? Start with Phase 2 items above!
Questions? Check testbuddy_debug.log for runtime errors.
Version: 1.0.0
Last Updated: December 2025