Architecture¶
System Overview¶
+--------------------+ +--------------------+
| Mathy.app | HTTP | mathy-server |
| (Swift/SwiftUI) | <---------------> | (Python/FastAPI) |
| | localhost:8765 | |
| - Menu bar UI | | - pix2tex model |
| - Screen capture | POST /predict | - Loaded once |
| - Hotkey | ---------------> | - Fast inference |
| - KaTeX preview | | |
| - History | {"latex": "..."} | |
| | <--------------- | |
+--------------------+ +--------------------+
The Swift app manages the Python server's full lifecycle — launching it on startup, polling its health endpoint, auto-restarting on crash, and terminating it on quit. All communication happens over HTTP on localhost:8765.
Capture Pipeline¶
When the user presses Cmd+Shift+M, the following pipeline executes:
KeyboardShortcuts.onKeyUp
-> AppState.startCapture()
-> ScreenCaptureManager.captureRegion() # AppleScript -> screencapture -i -s
-> saves PNG to ~/Library/Application Support/Mathy/images/
-> OCRService.predict(imageURL) # POST multipart/form-data to /predict
-> server runs pix2tex model on image
-> returns {"latex": "\\frac{1}{2}"}
-> ClipboardManager.copy(latex) # NSPasteboard.general
-> HistoryStore.add(record) # Prepend to history.json (max 100)
-> PreviewPopupView # Floating NSPanel with image + KaTeX render
The entire flow is async and runs on @MainActor, with blocking operations (process launches, network requests) dispatched to background threads. Guards prevent concurrent captures (isCapturing) and overlapping OCR requests (isProcessing).
App Lifecycle¶
Entry point: MathyApp.swift — a SwiftUI App struct with two scenes:
MenuBarExtra("Mathy", image: "MenuBarIcon")— the menu bar dropdown (.windowstyle)Settings { SettingsView }— the system settings window
AppState (@MainActor ObservableObject) is the central coordinator that owns all managers. On init:
- Registers the capture hotkey via KeyboardShortcuts
- Subscribes to server status changes via Combine
- Calls
checkSetupAndStart()which checks if the managed Python venv exists:- If ready: starts the server immediately
- If not: shows the onboarding window
Managers owned by AppState:
| Manager | Responsibility |
|---|---|
ServerManager |
Python server process lifecycle |
ScreenCaptureManager |
AppleScript-based screen region capture |
OCRService |
HTTP client to /predict endpoint |
ClipboardManager |
NSPasteboard wrapper |
HistoryStore |
JSON persistence of conversion records |
HotkeyManager |
Placeholder for future hotkey expansion |
PythonEnvironmentManager |
Venv creation and pix2tex installation |
Threading & Concurrency¶
All state-holding classes use @MainActor isolation to ensure UI consistency:
| Component | Isolation | Notes |
|---|---|---|
| AppState | @MainActor |
All state mutations on main thread |
| ServerManager | @MainActor |
Process launches dispatched to DispatchQueue.global() |
| PythonEnvironmentManager | @MainActor |
Process execution via withCheckedContinuation |
| HistoryStore | @MainActor |
File I/O is synchronous (small JSON, acceptable) |
| OCRService | None | URLSession handles threading internally |
| Hotkey callbacks | Any -> @MainActor |
Wrapped in Task { @MainActor in } |
Key concurrency patterns:
- Process execution:
withCheckedContinuation+DispatchQueue.global(qos: .userInitiated)bridges synchronousProcesscalls to async/await - Pipe safety: All process helpers use
readabilityHandlerto drain stdout/stderr asynchronously, preventing deadlock when child output exceeds the 64KB pipe buffer. Output collection is protected byNSLock. - Retain cycle prevention:
[weak self]in closures, NotificationCenter observers for window cleanup
Data Flow¶
~/Library/Application Support/Mathy/
+-- venv/ # Managed Python environment
| +-- bin/python3 # Used to launch server
+-- images/ # Captured screenshots
| +-- capture_{timestamp}.png # Persistent copies
+-- history.json # Conversion records (max 100)
All persistent data lives under ~/Library/Application Support/Mathy/. The Constants.appSupportDirectory property creates this directory on first access.