View my SDLC journey. github.com/jschilling12
by Jordan Schilling
Implemented a Pomodoro Timer application with a graphical user interface using Tkinter. This marked a significant step into GUI development and desktop application architecture.
Since this was new territory, the goal was to learn Tkinter properly—understanding how its event loop works, how timers behave, and how GUI-driven applications differ from traditional scripts. The Pomodoro Timer served as a practical theme to explore these concepts.
This effort also became the integration point for running time tracking in the background, tying together multiple systems into a single cohesive application.
This development session packed in a large number of concepts and architectural decisions:
Identified the need for two timers:
Initially, almost nothing worked correctly. This led directly into learning about threading.
To keep the GUI responsive while tracking application usage in the background, threading was required.
Using threads allowed:
The background worker thread used for time tracking is a daemon thread.
GUI frameworks are not thread-safe — UI logic must stay on the main thread.
Thread(target=self.root.mainloop()) # WRONG
Why this fails:
None as the thread targetThread(target=tracker.timed_process)
This refactor clearly separated responsibilities across components:
| Component | Responsibility |
|---|---|
| PomodoroTimer | UI + countdown logic |
| timeTracker | Background window time tracking |
| saveFiles | File system persistence |
This separation eliminated a large class of bugs and made reasoning about the system much easier.
Closing a Tkinter window does not automatically stop background threads.
WM_DELETE_WINDOWdef on_close():
tracker.stop()
saves.save_time_tracking(txt, tracker.time_tracking)
app.root.destroy()
app.root.protocol("WM_DELETE_WINDOW", on_close)
This guarantees:
Windows process queries are not always valid. Defensive checks were required to handle None and invalid process states.
def activeWindow():
try:
window = win32gui.GetForegroundWindow()
if not window:
return None
_, pid = win32process.GetWindowThreadProcessId(window)
if not pid or pid == 0:
return None
handle = win32api.OpenProcess(
win32con.PROCESS_QUERY_LIMITED_INFORMATION,
False,
pid
)
if not handle:
return None
This was necessary to prevent crashes when a foreground window could not be resolved.
Configuration storage was rewritten to follow a more industry-standard approach.
CONFIG_PATH = Path(os.environ["APPDATA"]) / "pomodoro_tracker" / "empty.txt"
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
%APPDATA% is stable across launches and updates%APPDATA%mainloopWhile Running
On Window Close
This is production-grade desktop application behavior.
This was a full coding marathon day with a steep learning curve. One AI assist was used specifically for background threading concepts, but the knowledge gained will carry forward into future projects.