Configuration Guide

Study Timer

πŸ“„ 110 Pages 🎯 Stage: Use It Today πŸ“¦ Output: configuration.py

What this page does

Imports the core Tkinter module that provides all windowing and widget capabilities.

Part: Configuration

Section: Module & Runtime Boundary

Depends on: None

This step is part of the larger process of establishing which tools the application is allowed to use.

Code (this page)

python
import tkinter as tk

Explanation

  • tkinter is Python's standard GUI library
  • Aliased as tk for shorter references throughout the code
  • Provides windows, buttons, labels, entries, and layout management
  • No application logic runs yetβ€”this only makes GUI tools available

Why this matters

Without this import, Python cannot create windows or any visual interface. This single line unlocks the entire GUI capability of the application.

This page will later include a diagram showing tkinter as the GUI foundation.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • ModuleNotFoundError: No module named 'tkinter' β€” Tkinter not installed
  • Using Tkinter instead of tkinter (Python 3 uses lowercase)
How to recover:
  • Verify Python installation includes Tkinter (standard on most systems)
  • Use lowercase tkinter

What this page does

Imports the messagebox submodule for displaying dialog boxes to the user.

Part: Configuration

Section: Module & Runtime Boundary

Depends on: Page 1

This step is part of the larger process of establishing user feedback capabilities.

Code (this page)

python
from tkinter import messagebox

Explanation

  • messagebox provides showerror(), showinfo(), and showwarning() functions
  • Used for validation feedback and success confirmations
  • Modal dialogs that require user acknowledgment
  • Separate import because it's a tkinter submodule

Why this matters

Configuration requires validation feedback. When users enter invalid settings, we need to tell them. Without `messagebox`, we would fail silently or need custom dialog code.

This page will later include a screenshot of an example messagebox dialog.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Writing import messagebox without the from tkinter prefix
  • Capitalization error (MessageBox vs messagebox)
How to recover:
  • Use exact form: from tkinter import messagebox

What this page does

Imports the time module for clock display and accurate countdown timing.

Part: Configuration

Section: Module & Runtime Boundary

Depends on: Page 2

This step is part of the larger process of enabling time-based functionality.

Code (this page)

python
import time

Explanation

  • time.strftime() formats the current time for the clock display
  • time.monotonic() provides a clock that never goes backward
  • Standard library module with no external dependencies
  • Essential for both display and countdown accuracy

Why this matters

Without the time module, we couldn't display the current time or measure elapsed time accurately. The monotonic clock is critical for drift-free countdown timing.

This page will later include a diagram comparing wall-clock time vs monotonic time.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Naming a local file time.py (shadows the module)
How to recover:
  • Ensure no local file named time.py exists in your directory

What this page does

Imports the Optional type hint for expressing nullable values.

Part: Configuration

Section: Module & Runtime Boundary

Depends on: Page 3

This step is part of the larger process of establishing type safety.

Code (this page)

python
from typing import Optional

Explanation

  • Optional[int] means "either an int or None"
  • Used for timing variables that may be unset
  • Used for validation functions that return None on failure
  • Improves code clarity and enables IDE support

Why this matters

Without Optional, return types like "int or None" would be ambiguous. This type hint documents the validation contract directly in function signatures.

This page will later include a diagram showing Optional[int] semantics.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Misspelling as optional (must be capitalized)
  • Forgetting the from typing prefix
How to recover:
  • Use exact form: from typing import Optional

What this page does

Imports the dataclass decorator for defining the Settings configuration object.

Part: Configuration

Section: Module & Runtime Boundary

Depends on: Page 4

This step is part of the larger process of enabling clean configuration objects.

Code (this page)

python
from dataclasses import dataclass

Explanation

  • @dataclass automatically generates __init__, __repr__, and equality methods
  • Reduces boilerplate for data-holding classes
  • Settings is a pure data containerβ€”exactly what dataclass is designed for
  • Standard library module (Python 3.7+)

Why this matters

Without dataclass, we would manually write initialization code for Settings. The decorator guarantees correct, consistent implementations with less code.

This page will later include a comparison of dataclass vs manual class definition.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Python version < 3.7 (dataclasses not available)
  • Misspelling as dataclass in the module name (it's dataclasses plural)
How to recover:
  • Ensure Python 3.7+ is being used
  • Module is dataclasses, decorator is dataclass

What this page does

Creates the configuration object that holds all user-adjustable settings.

Part: Configuration

Section: Settings Domain Model

Depends on: Page 5

This step is part of the larger process of separating configuration from runtime behavior.

Code (this page)

python
@dataclass
class Settings:
    """Configuration state."""
    default_duration_seconds: int = 25 * 60  # 25 minutes
    alarm_enabled: bool = True

Explanation

  • @dataclass generates initialization and representation methods
  • default_duration_seconds stores countdown duration in seconds (1500 = 25 minutes)
  • alarm_enabled controls whether the bell sounds on completion
  • Default values match traditional Pomodoro timing
  • This is the single source of truth for all configuration

Why this matters

Without a Settings object, configuration would be scattered across multiple variables. This groups related settings together, making them easy to display in UI, validate, and manage.

This page will later include a diagram showing Settings as the configuration hub.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Missing @dataclass decorator
  • Wrong default value types (string instead of int)
How to recover:
  • Ensure @dataclass appears directly above class Settings:
  • Use int for duration, bool for alarm_enabled

What this page does

Creates a dedicated object to hold all mutable runtime state of the countdown timer.

Part: Configuration

Section: Domain State

Depends on: Page 6

This step is part of the larger process of separating runtime behavior from configuration.

Code (this page)

python
class TimerState:
    """Transient runtime state for the countdown timer."""

    def __init__(self, duration_seconds: int):
        self.remaining_seconds: int = duration_seconds
        self.is_running: bool = False
        self.alarm_triggered: bool = False

Explanation

  • remaining_seconds tracks how much time is left on the countdown
  • is_running indicates whether the timer is actively counting down
  • alarm_triggered prevents the alarm from firing more than once
  • Takes initial duration as parameter (comes from Settings)
  • This object is never saved to diskβ€”it exists only while running

Why this matters

Without a dedicated state object, timer variables would be scattered. This creates a single source of truth for runtime behavior. State transitions (start, pause, reset, alarm) all modify this one object.

This page will later include a state diagram showing the three fields and their relationships.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Missing colon after class definition
  • Forgetting self parameter in __init__
How to recover:
  • Verify class syntax matches exactly
  • Check that all three attributes are assigned inside __init__

What this page does

Defines the main application controller that owns the window, state, and behavior.

Part: Configuration

Section: Application Shell

Depends on: Page 7

This step is part of the larger process of establishing architectural authority.

Code (this page)

python
class StudyTimerApp:
    """Main application controller."""

Explanation

  • All application behavior will live inside this class
  • This object coordinates UI, timing, settings, and state
  • External code interacts only through this controller
  • No logic is executed yetβ€”this establishes structure

Why this matters

Without a central controller, behavior would be distributed across functions and global variables. This class becomes the single entry point for understanding the application.

This page will later include a box diagram showing StudyTimerApp as the container.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Missing colon after class name
  • Incorrect capitalization
How to recover:
  • Use PascalCase: StudyTimerApp
  • Include colon after class name

What this page does

Declares fixed timing values used throughout the application.

Part: Configuration

Section: Application Shell

Depends on: Page 8

This step is part of the larger process of centralizing magic numbers.

Code (this page)

python
# Timing constants
    TICK_INTERVAL_MS = 200
    CLOCK_UPDATE_MS = 1000

Explanation

  • TICK_INTERVAL_MS controls how often the timer logic runs (200ms = 5 times per second)
  • CLOCK_UPDATE_MS controls how often the clock display updates (1000ms = once per second)
  • Constants are defined once and reused everywhere
  • Note: No DEFAULT_DURATION constantβ€”Configuration uses Settings instead

Why this matters

Without named constants, 200 and 1000 would appear scattered throughout the code. Changing timing behavior would require hunting through multiple locations. This centralizes timing decisions.

This page will later include a table showing each constant and where it's used.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Constants defined outside the class (wrong indentation)
  • Using lowercase names (violates convention)
How to recover:
  • Indent constants inside the class body
  • Use SCREAMING_SNAKE_CASE for constants

What this page does

Creates the initialization method that sets up the application.

Part: Configuration

Section: Application Shell

Depends on: Page 9

This step is part of the larger process of establishing the startup sequence.

Code (this page)

python
def __init__(self, root: tk.Tk):
        self.root = root
        self.root.title("Academic Study Timer")
        self.root.resizable(False, False)

Explanation

  • Stores the Tk root window for later use
  • Sets a descriptive window title
  • Prevents resizing to preserve layout integrity
  • Type hint tk.Tk documents the expected argument

Why this matters

Without storing the root window, later methods cannot schedule callbacks or modify the window. Without disabling resize, users could break the layout.

This page will later include a screenshot of the empty window with title bar.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Forgetting self parameter
  • Typo in method name (__init_ vs __init__)
How to recover:
  • Verify __init__ has both self and root parameters
  • Use double underscores on both sides

What this page does

Creates the Settings instance that holds all user-configurable values.

Part: Configuration

Section: Application Initialization

Depends on: Page 10, Page 6 (Settings class exists)

This step is part of the larger process of preparing configuration before UI construction.

Code (this page)

python
# Settings (in-memory only for Configuration stage)
        self.settings = Settings()

Explanation

  • Instantiates Settings with default values (25 minutes, alarm enabled)
  • Becomes the runtime configuration source of truth
  • UI will read from and write to this object
  • Comment clarifies this is in-memory only (Persistence adds disk storage)

Why this matters

Without instantiating Settings, there would be no configuration to display or modify. This line creates the object that the entire settings UI interacts with.

This page will later include a diagram showing self.settings as the configuration source.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Forgetting self. prefix (variable not accessible later)
  • Settings class not defined before StudyTimerApp
How to recover:
  • Use self.settings to store on instance
  • Ensure Settings class appears before StudyTimerApp in the file

What this page does

Creates the initial runtime timer state using the configured default duration.

Part: Configuration

Section: Application Initialization

Depends on: Page 11 (settings exists), Page 7 (TimerState class exists)

This step is part of the larger process of connecting configuration to runtime behavior.

Code (this page)

python
# Timer state
        self.timer_state = TimerState(self.settings.default_duration_seconds)

Explanation

  • Instantiates TimerState with duration from Settings (not a hardcoded constant)
  • Sets remaining_seconds to the configured default (1500 seconds = 25 minutes)
  • Initializes timer in a non-running, non-alarmed state
  • This connection means changing settings can affect timer behavior

Why this matters

Without using Settings for initialization, the timer would ignore user configuration. This is the bridge between what the user configures and what the timer actually does.

This page will later include a diagram showing Settings β†’ TimerState initialization flow.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Using a hardcoded number instead of self.settings.default_duration_seconds
  • Initializing TimerState before Settings exists
How to recover:
  • Always use self.settings.default_duration_seconds for initial duration
  • Ensure Settings() is created before TimerState()

What this page does

Sets up internal variables used for accurate time tracking.

Part: Configuration

Section: Application Initialization

Depends on: Page 12

This step is part of the larger process of enabling drift-free countdown timing.

Code (this page)

python
# Timing bookkeeping
        self._last_tick_time: Optional[float] = None
        self._accumulated_time: float = 0.0

Explanation

  • _last_tick_time stores the previous tick timestamp (None when not running)
  • _accumulated_time stores fractional seconds between ticks
  • Both prefixed with _ to indicate internal use
  • These prevent timing drift and handle tick jitter

Why this matters

Without tracking elapsed time between ticks, the countdown would drift based on UI responsiveness. Without accumulating fractional seconds, the timer would lose precision.

This page will later include a timeline diagram showing tick intervals and accumulated time.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Missing Optional import from typing
  • Using wrong type annotation syntax
How to recover:
  • Verify from typing import Optional is in imports
  • Use Optional[float] with brackets, not parentheses

What this page does

Delegates all UI creation to a dedicated method.

Part: Configuration

Section: Application Initialization

Depends on: Page 13

This step is part of the larger process of separating initialization from layout.

Code (this page)

python
# Build UI
        self._build_ui()

Explanation

  • Calls a separate method to construct all widgets
  • Keeps __init__ readable and linear
  • Ensures UI is built after all state is initialized
  • Method will be defined later in the code

Why this matters

Without delegation, `__init__` would grow to hundreds of lines. Separating "prepare data" from "build visuals" keeps code organized and maintainable.

This page will later include a flowchart showing __init__ calling _build_ui.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • AttributeError if method not yet defined (expected at this stage)
  • Missing parentheses: _build_ui vs _build_ui()
How to recover:
  • This error resolves when _build_ui is implemented
  • Ensure parentheses are present for method call

What this page does

Starts the recurring clock update and timer tick loops.

Part: Configuration

Section: Application Initialization

Depends on: Page 14

This step is part of the larger process of enabling continuous, non-blocking updates.

Code (this page)

python
# Start background updates
        self._update_clock()
        self._tick()

Explanation

  • _update_clock() updates the live time display every second
  • _tick() drives the countdown logic at regular intervals
  • Both methods reschedule themselves using root.after()
  • No threads are createdβ€”all execution stays in Tkinter's event loop

Why this matters

Without background updates, the clock would freeze and the timer would never count down. Using `root.after()` keeps the UI responsive without threading complexity.

This page will later include a diagram showing the event loop with scheduled callbacks.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Methods not yet defined (expected at this stage)
  • Missing parentheses on method calls
How to recover:
  • Errors resolve when methods are implemented
  • Ensure both calls include parentheses

What this page does

Creates the top-level layout frame that holds all widgets.

Part: Configuration

Section: UI Composition

Depends on: Page 14 (_build_ui is called)

This step is part of the larger process of establishing the widget hierarchy.

Code (this page)

python
def _build_ui(self):
        """Construct all UI elements."""
        # Main container
        main_frame = tk.Frame(self.root, padx=20, pady=20)
        main_frame.pack(fill=tk.BOTH, expand=True)

Explanation

  • main_frame serves as the root container for all UI elements
  • Padding (padx, pady) creates visual spacing from window edges
  • fill and expand allow the frame to occupy all available space
  • All subsequent UI elements will attach to this frame

Why this matters

Without a container frame, widgets would attach directly to the root window, making layout changes difficult. This establishes a single containment hierarchy.

This page will later include a wireframe showing the main_frame with padding margins.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Forgetting self parameter in method definition
  • Using wrong parent (not self.root)
How to recover:
  • Verify method signature is def _build_ui(self):
  • Ensure frame's parent is self.root

What this page does

Creates a horizontal container for the live clock display.

Part: Configuration

Section: UI Composition β†’ Header

Depends on: Page 16 (main_frame exists)

This step is part of the larger process of building the header section.

Code (this page)

python
# Clock header
        clock_frame = tk.Frame(main_frame)
        clock_frame.pack(fill=tk.X, pady=(0, 15))

Explanation

  • clock_frame contains the clock label and value
  • fill=tk.X stretches horizontally to match parent width
  • pady=(0, 15) adds 15 pixels of space below (asymmetric padding)
  • Separates clock from the timer display below

Why this matters

Without a dedicated frame, clock elements would mix with timer elements. The bottom padding creates clear visual separation between header and body.

This page will later include a wireframe showing clock_frame position.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Using wrong parent (self.root instead of main_frame)
  • Symmetric padding instead of asymmetric (0, 15)
How to recover:
  • Ensure parent is main_frame (local variable)
  • Use tuple syntax: pady=(top, bottom)

What this page does

Adds the "Current Time:" text label that never changes.

Part: Configuration

Section: UI Composition β†’ Header

Depends on: Page 17 (clock_frame exists)

This step is part of the larger process of labeling dynamic UI elements.

Code (this page)

python
tk.Label(
            clock_frame,
            text="Current Time:",
            font=("TkDefaultFont", 10)
        ).pack(side=tk.LEFT)

Explanation

  • Creates a Label widget with static text
  • Uses system default font at size 10
  • Packs to the left side of clock_frame
  • Not stored in a variableβ€”never needs updating

Why this matters

Without a label, users would see a time value with no explanation. Left-packing ensures the label aligns with the dynamic value that follows.

This page will later include a screenshot showing "Current Time:" text.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Wrong parent (not clock_frame)
  • Missing pack() call
How to recover:
  • Ensure parent is clock_frame
  • Chain .pack() directly after Label creation

What this page does

Creates the label that displays the actual current time, updated every second.

Part: Configuration

Section: UI Composition β†’ Header

Depends on: Page 18 (static label exists)

This step is part of the larger process of displaying dynamic content.

Code (this page)

python
self.clock_label = tk.Label(
            clock_frame,
            text="",
            font=("TkDefaultFont", 10, "bold")
        )
        self.clock_label.pack(side=tk.LEFT, padx=(5, 0))

Explanation

  • Stored as self.clock_label for later updates by _update_clock()
  • Bold font differentiates dynamic content from static label
  • Left padding separates it from "Current Time:"
  • Initial text is emptyβ€”populated by the update method

Why this matters

Without storing as instance variable, `_update_clock` couldn't modify the text. This creates the target for the clock update loop.

This page will later include a screenshot showing the clock value.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Forgetting self. prefix
  • Bold not in font tuple
How to recover:
  • Use self.clock_label to store on instance
  • Font tuple with bold: ("FontName", size, "bold")

What this page does

Creates the bordered container that holds the countdown display and status.

Part: Configuration

Section: UI Composition β†’ Countdown Display

Depends on: Page 17 (header complete)

This step is part of the larger process of visually grouping the timer area.

Code (this page)

python
# Timer display frame
        self.timer_frame = tk.Frame(
            main_frame,
            bd=2,
            relief=tk.GROOVE,
            padx=20,
            pady=20
        )
        self.timer_frame.pack(fill=tk.X, pady=(0, 15))

Explanation

  • Stored as self.timer_frame for alarm color changes later
  • bd=2 creates a 2-pixel border
  • relief=tk.GROOVE gives an inset appearance
  • Internal padding separates content from borders
  • Bottom margin separates from controls below

Why this matters

Without a bordered frame, the countdown would float in empty space. Storing as instance variable enables alarm state to change the background color.

This page will later include a screenshot showing the grooved border.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Forgetting self. prefix
  • Using wrong relief style
How to recover:
  • Store as self.timer_frame for alarm functionality
  • Use tk.GROOVE (not string "groove")

What this page does

Creates the large label that shows the remaining time in MM:SS format.

Part: Configuration

Section: UI Composition β†’ Countdown Display

Depends on: Page 20 (timer_frame exists)

This step is part of the larger process of displaying the countdown prominently.

Code (this page)

python
# Countdown label
        self.countdown_label = tk.Label(
            self.timer_frame,
            text=self._format_time(self.timer_state.remaining_seconds),
            font=("TkFixedFont", 48, "bold")
        )
        self.countdown_label.pack()

Explanation

  • Stored as self.countdown_label for countdown updates
  • Large font size (48) makes time highly visible
  • Uses _format_time() to display initial value as MM:SS
  • Fixed-width font ensures consistent digit spacing
  • Centered within timer_frame by default pack behavior

Why this matters

Without a large, prominent display, users couldn't see the countdown at a glance. Storing as instance variable enables the tick loop to update the display.

This page will later include a screenshot showing "25:00" in large text.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • _format_time not yet defined (expected)
  • Wrong parent (should be self.timer_frame)
How to recover:
  • Error resolves when _format_time is implemented
  • Ensure parent is self.timer_frame

What this page does

Creates the label that shows the current timer state (Ready, Running, Paused, Time's Up).

Part: Configuration

Section: UI Composition β†’ Countdown Display

Depends on: Page 21 (countdown label exists)

This step is part of the larger process of communicating timer state.

Code (this page)

python
# Status label
        self.status_label = tk.Label(
            self.timer_frame,
            text="Ready",
            font=("TkDefaultFont", 12)
        )
        self.status_label.pack(pady=(10, 0))

Explanation

  • Stored as self.status_label for state change updates
  • Smaller font than countdown (12 vs 48)β€”secondary information
  • Initial text "Ready" matches initial state
  • Top padding separates from countdown display

Why this matters

Without a status label, users would have to infer state from button availability. This explicitly communicates what the timer is doing.

This page will later include a screenshot showing "Ready" below countdown.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Wrong parent widget
  • Forgetting self. prefix
How to recover:
  • Ensure parent is self.timer_frame
  • Store as self.status_label

What this page does

Creates a horizontal container for the control buttons.

Part: Configuration

Section: UI Composition β†’ Controls

Depends on: Page 20 (timer display complete)

This step is part of the larger process of organizing control elements.

Code (this page)

python
# Control buttons
        controls_frame = tk.Frame(main_frame)
        controls_frame.pack(fill=tk.X, pady=(0, 15))

Explanation

  • Local variableβ€”buttons are stored individually instead
  • fill=tk.X stretches to parent width for button distribution
  • Bottom padding separates from settings section below
  • Contains Start, Stop, and Reset buttons

Why this matters

Without a container frame, buttons would need individual positioning. This groups controls and enables even distribution across the width.

This page will later include a wireframe showing controls_frame.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Wrong parent (should be main_frame)
  • Missing bottom padding
How to recover:
  • Ensure parent is main_frame
  • Add pady=(0, 15) for separation from settings

What this page does

Creates the button that starts or resumes the countdown.

Part: Configuration

Section: UI Composition β†’ Controls

Depends on: Page 23 (controls_frame exists)

This step is part of the larger process of implementing user control.

Code (this page)

python
self.start_button = tk.Button(
            controls_frame,
            text="Start",
            width=10,
            command=self._on_start
        )
        self.start_button.pack(side=tk.LEFT, expand=True)

Explanation

  • Stored as self.start_button for state-based enabling/disabling
  • Fixed width of 10 characters for consistent sizing
  • command binds to _on_start method
  • expand=True with side=tk.LEFT distributes space evenly

Why this matters

Without storing as instance variable, the button couldn't be disabled during countdown. This creates the primary action trigger.

This page will later include a screenshot showing the Start button.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • _on_start not yet defined (expected)
  • Missing expand=True
How to recover:
  • Command reference errors resolve when method is implemented
  • Include expand=True for even distribution

What this page does

Creates the button that pauses the countdown.

Part: Configuration

Section: UI Composition β†’ Controls

Depends on: Page 24 (Start button exists)

This step is part of the larger process of implementing user control.

Code (this page)

python
self.stop_button = tk.Button(
            controls_frame,
            text="Stop",
            width=10,
            command=self._on_stop,
            state=tk.DISABLED
        )
        self.stop_button.pack(side=tk.LEFT, expand=True)

Explanation

  • Stored as self.stop_button for state management
  • Initially disabled (state=tk.DISABLED)β€”can't stop what isn't running
  • Same width as Start for visual consistency
  • command binds to _on_stop method

Why this matters

Without initial disabled state, users could click Stop before Start. This implements UI-level guard that matches logical constraints.

This page will later include a screenshot showing the disabled Stop button.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Forgetting state=tk.DISABLED
  • Using string "disabled" instead of constant
How to recover:
  • Always use tk.DISABLED constant
  • Verify button appears grayed out

What this page does

Creates the button that resets the timer to its initial state.

Part: Configuration

Section: UI Composition β†’ Controls

Depends on: Page 25 (Stop button exists)

This step is part of the larger process of implementing user control.

Code (this page)

python
self.reset_button = tk.Button(
            controls_frame,
            text="Reset",
            width=10,
            command=self._on_reset
        )
        self.reset_button.pack(side=tk.LEFT, expand=True)

Explanation

  • Stored as self.reset_button for potential state management
  • Initially enabledβ€”reset is always valid
  • Same width as other buttons for consistency
  • command binds to _on_reset method

Why this matters

Without a reset button, users would need to restart the application. This returns the timer to its starting condition.

This page will later include a screenshot showing all three buttons.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Buttons not evenly distributed
  • Width inconsistent
How to recover:
  • Ensure all three have expand=True
  • Use width=10 for all buttons

What this page does

Adds a labeled UI area that holds all configuration controls.

Part: Configuration

Section: UI Composition β†’ Settings Section

Depends on: Page 26 (controls complete)

This step is part of the larger process of building the configuration interface.

Code (this page)

python
# Settings section
        settings_frame = tk.LabelFrame(main_frame, text="Settings", padx=10, pady=10)
        settings_frame.pack(fill=tk.X, pady=(0, 15))

Explanation

  • LabelFrame provides a bordered container with a title
  • "Settings" label clearly identifies the section purpose
  • Internal padding creates visual breathing room
  • Bottom margin separates from Apply button below

Why this matters

Without a labeled container, configuration controls would blend with timer controls. This provides clear visual hierarchy and groups related inputs.

This page will later include a screenshot showing the Settings section.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Using Frame instead of LabelFrame
  • Wrong parent widget
How to recover:
  • Use tk.LabelFrame for titled container
  • Ensure parent is main_frame

What this page does

Creates the row that holds the duration label and entry field side-by-side.

Part: Configuration

Section: UI Composition β†’ Settings Section

Depends on: Page 27 (settings_frame exists)

This step is part of the larger process of building the duration input.

Code (this page)

python
# Duration setting
        duration_row = tk.Frame(settings_frame)
        duration_row.pack(fill=tk.X, pady=(0, 10))

Explanation

  • Row frame enables side-by-side layout
  • fill=tk.X stretches to match parent width
  • Bottom padding separates from alarm checkbox below
  • Container for label and entry widgets

Why this matters

Without a row container, the label and entry would stack vertically. This enables the natural "Label: [Input]" horizontal layout.

This page will later include a wireframe showing duration row layout.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Wrong parent (should be settings_frame)
How to recover:
  • Ensure parent is settings_frame

What this page does

Adds the "Default Duration (minutes):" text label.

Part: Configuration

Section: UI Composition β†’ Settings Section

Depends on: Page 28 (duration_row exists)

This step is part of the larger process of building the duration input.

Code (this page)

python
tk.Label(duration_row, text="Default Duration (minutes):").pack(side=tk.LEFT)

Explanation

  • Static label that never changes
  • Explicitly states the unit (minutes) to avoid confusion
  • Packed to the left side of the row
  • Not storedβ€”never needs updating

Why this matters

Without a label, users wouldn't know what the entry field is for. The explicit "(minutes)" prevents confusion with seconds.

This page will later include a screenshot showing the label.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Wrong parent widget
  • Missing side=tk.LEFT
How to recover:
  • Ensure parent is duration_row
  • Include side=tk.LEFT in pack()

What this page does

Creates the variable that backs the duration entry field.

Part: Configuration

Section: UI Composition β†’ Settings Section

Depends on: Page 29 (label exists), Page 11 (settings exists)

This step is part of the larger process of enabling two-way UI binding.

Code (this page)

python
self.duration_var = tk.StringVar(
            value=str(self.settings.default_duration_seconds // 60)
        )

Explanation

  • UI shows minutes, settings store seconds (conversion: // 60)
  • StringVar enables two-way binding with Entry widget
  • Initial value reflects current settings (25 minutes)
  • Stored as instance variable for access in _on_apply

Why this matters

Without a StringVar, reading the entry value would require calling `.get()` directly on the widget. The StringVar provides cleaner abstraction.

This page will later include a diagram showing StringVar ↔ Entry binding.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Using / instead of // (produces float)
  • Forgetting str() conversion
How to recover:
  • Use // for integer division
  • Wrap in str() for StringVar

What this page does

Adds the text field where the user types the default duration.

Part: Configuration

Section: UI Composition β†’ Settings Section

Depends on: Page 30 (duration_var exists)

This step is part of the larger process of capturing user input.

Code (this page)

python
self.duration_entry = tk.Entry(
            duration_row,
            textvariable=self.duration_var,
            width=8
        )
        self.duration_entry.pack(side=tk.LEFT, padx=(10, 0))

Explanation

  • textvariable creates two-way binding with duration_var
  • Width of 8 characters supports typical values comfortably
  • Left padding separates from the label
  • Stored as instance variable for potential styling

Why this matters

Without an entry field, users couldn't change the duration. The binding ensures StringVar reflects entry content.

This page will later include a screenshot showing the entry field.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Missing textvariable parameter
  • Wrong parent widget
How to recover:
  • Include textvariable=self.duration_var
  • Ensure parent is duration_row

What this page does

Creates the variable that tracks the alarm checkbox state.

Part: Configuration

Section: UI Composition β†’ Settings Section

Depends on: Page 27 (settings_frame exists), Page 11 (settings exists)

This step is part of the larger process of enabling the alarm toggle.

Code (this page)

python
# Alarm toggle
        self.alarm_var = tk.BooleanVar(value=self.settings.alarm_enabled)

Explanation

  • BooleanVar is the appropriate type for checkbox binding
  • Initial value reflects current settings (True by default)
  • Stored as instance variable for access in _on_apply
  • Will be read when Apply button is clicked

Why this matters

Without a BooleanVar, reading the checkbox state would be more complex. This provides a clean interface for the boolean value.

This page will later include a diagram showing BooleanVar ↔ Checkbutton binding.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Using StringVar instead of BooleanVar
  • Wrong initial value source
How to recover:
  • Use tk.BooleanVar for boolean values
  • Use self.settings.alarm_enabled for initial value

What this page does

Adds the checkbox that controls whether the alarm sound is enabled.

Part: Configuration

Section: UI Composition β†’ Settings Section

Depends on: Page 32 (alarm_var exists)

This step is part of the larger process of building the alarm toggle.

Code (this page)

python
self.alarm_check = tk.Checkbutton(
            settings_frame,
            text="Enable alarm sound",
            variable=self.alarm_var
        )
        self.alarm_check.pack(anchor=tk.W)

Explanation

  • Checkbutton provides a standard checkbox widget
  • variable binding connects to alarm_var
  • Text describes what the checkbox controls
  • anchor=tk.W aligns to the left (west)

Why this matters

Without a checkbox, users couldn't disable the alarm sound. This provides a simple on/off toggle.

This page will later include a screenshot showing the checkbox.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Missing variable parameter
  • Wrong parent widget
How to recover:
  • Include variable=self.alarm_var
  • Ensure parent is settings_frame

What this page does

Creates the frame that holds the Apply Settings button.

Part: Configuration

Section: UI Composition β†’ Apply Controls

Depends on: Page 27 (settings section complete)

This step is part of the larger process of building the configuration commit mechanism.

Code (this page)

python
# Apply button
        apply_frame = tk.Frame(main_frame)
        apply_frame.pack(fill=tk.X)

Explanation

  • Separate frame keeps Apply button distinct from inputs
  • fill=tk.X allows for potential additional buttons
  • Positioned after settings section
  • No padding neededβ€”button defines spacing

Why this matters

Without a container, the Apply button would be placed directly in another frame. This maintains clean visual hierarchy.

This page will later include a wireframe showing apply_frame position.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Wrong parent (should be main_frame)
How to recover:
  • Ensure parent is main_frame

What this page does

Adds the button that validates and applies UI settings into the live application.

Part: Configuration

Section: UI Composition β†’ Apply Controls

Depends on: Page 34 (apply_frame exists)

This step is part of the larger process of implementing the configuration commit workflow.

Code (this page)

python
self.apply_button = tk.Button(
            apply_frame,
            text="Apply Settings",
            width=15,
            command=self._on_apply
        )
        self.apply_button.pack(side=tk.LEFT)

Explanation

  • Explicit "commit" action for configuration changes
  • command connects to _on_apply handler
  • Width standardized for visual consistency
  • Enables validation before state update

Why this matters

Without an Apply button, changes would auto-apply (confusing) or never apply. The explicit commit gives users control.

This page will later include a screenshot showing the Apply button.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • _on_apply not yet defined (expected)
  • Wrong parent widget
How to recover:
  • Error resolves when handler is implemented
  • Ensure parent is apply_frame

What this page does

Converts raw seconds into human-readable MM:SS display format.

Part: Configuration

Section: Helper Utilities

Depends on: None (pure function)

This step is part of the larger process of displaying countdown values.

Code (this page)

python
def _format_time(self, seconds: int) -> str:
        """Convert seconds to MM:SS format."""
        minutes = seconds // 60
        secs = seconds % 60
        return f"{minutes:02d}:{secs:02d}"

Explanation

  • Integer division (//) extracts whole minutes
  • Modulo (%) extracts remaining seconds
  • f-string with :02d ensures zero-padded two-digit output
  • Returns string like "25:00" or "04:37"

Why this matters

Without formatting, users would see raw seconds (1500) instead of readable time (25:00). Zero-padding ensures consistent display width.

This page will later include input/output examples.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Using / instead of //
  • Missing zero-padding
How to recover:
  • Use // for integer division
  • Use :02d format specifier

What this page does

Creates the helper method that parses and validates duration input from the UI.

Part: Configuration

Section: Input & Validation Pipeline

Depends on: Page 30 (duration_var exists)

This step is part of the larger process of validating user input.

Code (this page)

python
def _parse_duration_input(self) -> Optional[int]:
        """Parse and validate duration entry. Returns seconds or None if invalid."""

Explanation

  • Centralizes input validation in one place
  • Returns seconds (internal unit) or None (invalid)
  • Type hint Optional[int] clarifies the contract
  • Keeps _on_apply() focused on coordination

Why this matters

Without centralized validation, parsing logic would be duplicated. This helper makes validation testable and clearly documented.

This page will later include a flowchart of validation logic.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Missing Optional import
  • Wrong return type annotation
How to recover:
  • Ensure from typing import Optional is in imports
  • Use Optional[int] for nullable int return

What this page does

Reads the Entry's StringVar and attempts to convert it to an integer.

Part: Configuration

Section: Input & Validation Pipeline

Depends on: Page 37 (method started)

This step is part of the larger process of validating duration input.

Code (this page)

python
try:
            minutes = int(self.duration_var.get().strip())

Explanation

  • .get() reads Entry content via StringVar
  • .strip() removes whitespace
  • int() enforces numeric input
  • Invalid input raises ValueError
  • try block catches conversion failures

Why this matters

Without parsing, the string couldn't be used for calculations. Without try/except, invalid input would crash the application.

This page will later include valid/invalid input examples.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Forgetting .strip()
  • Wrong variable reference
How to recover:
  • Always strip: .get().strip()
  • Use self.duration_var

What this page does

Rejects duration values that are too small.

Part: Configuration

Section: Input & Validation Pipeline

Depends on: Page 38 (minutes parsed)

This step is part of the larger process of enforcing valid ranges.

Code (this page)

python
if minutes < 1:
                return None

Explanation

  • Rejects zero and negative values
  • Minimum useful duration is 1 minute
  • Returns None to signal invalid input
  • Early return keeps code flat

Why this matters

Without a minimum check, users could set 0 minutes (immediate alarm) or negative values. One minute is the smallest practical session.

This page will later include valid range visualization.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Using <= instead of <
  • Forgetting return
How to recover:
  • Use < 1 to allow 1 as valid
  • Always return None for invalid

What this page does

Rejects duration values that are too large.

Part: Configuration

Section: Input & Validation Pipeline

Depends on: Page 39 (minimum validated)

This step is part of the larger process of enforcing valid ranges.

Code (this page)

python
if minutes > 999:
                return None

Explanation

  • Rejects unreasonably large values
  • 999 minutes (~16.5 hours) is practical upper limit
  • Entry field width accommodates this range
  • Returns None to signal invalid input

Why this matters

Without a maximum check, users could enter values that overflow displays. 999 provides ample range for any study session.

This page will later include complete valid range.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Using >= instead of >
  • Wrong threshold
How to recover:
  • Use > 999 to allow 999
  • Match threshold to UI field width

What this page does

Returns the validated duration converted to seconds.

Part: Configuration

Section: Input & Validation Pipeline

Depends on: Page 40 (range validated)

This step is part of the larger process of providing validated output.

Code (this page)

python
return minutes * 60

Explanation

  • Internal model stores duration in seconds
  • Conversion happens once, at validation time
  • Returns value ready for Settings
  • Only reached if all validation passed

Why this matters

Without conversion, the caller would handle unit conversion. Doing it here ensures the return is always in seconds.

This page will later include unit conversion diagram.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Forgetting return
  • Wrong conversion factor
How to recover:
  • Always return the value
  • Use * 60 for minutes to seconds

What this page does

Converts invalid parse attempts into None return value.

Part: Configuration

Section: Input & Validation Pipeline

Depends on: Page 38 (try block started)

This step is part of the larger process of handling invalid input.

Code (this page)

python
except ValueError:
            return None

Explanation

  • Catches ValueError from int() conversion
  • Non-numeric input becomes None
  • No crash, no exception propagation
  • Caller handles None as "invalid"

Why this matters

Without exception handling, typing "abc" would crash the application. This ensures graceful handling of any input.

This page will later include ValueError trigger examples.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Catching wrong exception
  • Missing return None
How to recover:
  • Catch ValueError specifically
  • Always return None in except

What this page does

Creates the method that updates the live clock display every second.

Part: Configuration

Section: Background Processes

Depends on: Page 19 (clock_label exists)

This step is part of the larger process of providing real-time feedback.

Code (this page)

python
def _update_clock(self):
        """Update the live clock display."""
        current_time = time.strftime("%H:%M:%S")
        self.clock_label.config(text=current_time)
        self.root.after(self.CLOCK_UPDATE_MS, self._update_clock)

Explanation

  • time.strftime("%H:%M:%S") gets current time in 24-hour format
  • config(text=...) updates the label
  • root.after() schedules next update without blocking
  • Method reschedules itself through the event loop

Why this matters

Without `root.after()`, the update would block the UI. Self-rescheduling creates a continuous, non-blocking update cycle.

This page will later include the after() cycle diagram.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Missing self. on clock_label
  • Forgetting to reschedule
How to recover:
  • Use self.clock_label
  • Always include root.after() at end

What this page does

Creates the entry point for the recurring timer tick loop.

Part: Configuration

Section: Time Engine

Depends on: Page 15 (tick started)

This step is part of the larger process of driving the countdown.

Code (this page)

python
def _tick(self):
        """Process one tick of the countdown timer."""

Explanation

  • Method runs continuously via root.after()
  • Contains all countdown logic
  • Executes regardless of timer state
  • Docstring clarifies single-tick responsibility

Why this matters

Without a dedicated tick method, countdown logic would be scattered. This centralizes all timing behavior.

This page will later include a tick loop flowchart.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Typo in method name
  • Missing self parameter
How to recover:
  • Use exact name: _tick
  • Include self as first parameter

What this page does

Guards countdown logic to only execute when timer is running.

Part: Configuration

Section: Time Engine

Depends on: Page 44 (tick method exists)

This step is part of the larger process of implementing state-aware countdown.

Code (this page)

python
if self.timer_state.is_running:
            current_time = time.monotonic()

Explanation

  • is_running check prevents countdown when paused
  • time.monotonic() provides clock immune to adjustments
  • Monotonic time never goes backward
  • Only captures timestamp when actually running

Why this matters

Without the guard, timer would count down even when paused. Monotonic time prevents system clock changes from corrupting countdown.

This page will later include monotonic vs wall-clock comparison.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Using time.time() instead
  • Checking wrong variable
How to recover:
  • Always use time.monotonic() for elapsed time
  • Check timer_state.is_running

What this page does

Computes the real time elapsed since the previous tick.

Part: Configuration

Section: Time Engine

Depends on: Page 45 (current_time captured)

This step is part of the larger process of drift-free timing.

Code (this page)

python
if self._last_tick_time is not None:
                elapsed = current_time - self._last_tick_time
                self._accumulated_time += elapsed

Explanation

  • Checks previous tick exists (not first tick)
  • Calculates actual elapsed time between ticks
  • Accumulates fractional seconds for precision
  • Handles variable tick intervals gracefully

Why this matters

Without elapsed calculation, countdown would assume perfect 200ms ticks. Accumulation ensures real-time accuracy.

This page will later include accumulated time diagram.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Not checking for None
  • Using = instead of +=
How to recover:
  • Always check is not None first
  • Use += for accumulation

What this page does

Converts accumulated time into countdown decrements.

Part: Configuration

Section: Time Engine

Depends on: Page 46 (time accumulated)

This step is part of the larger process of whole-second countdown.

Code (this page)

python
while self._accumulated_time >= 1.0 and self.timer_state.remaining_seconds > 0:
                self.timer_state.remaining_seconds -= 1
                self._accumulated_time -= 1.0

Explanation

  • while handles multiple seconds if tick delayed
  • Only decrements when full second accumulated
  • Stops at zero to prevent negative
  • Preserves fractional remainder

Why this matters

Without while loop, delayed tick could skip seconds. Zero check prevents negative countdown.

This page will later include decrement diagram.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Using if instead of while
  • Missing zero check
How to recover:
  • Use while for multiple seconds
  • Include remaining_seconds > 0

What this page does

Checks if countdown reached zero and alarm hasn't fired.

Part: Configuration

Section: Time Engine

Depends on: Page 47 (countdown decremented)

This step is part of the larger process of triggering completion.

Code (this page)

python
if self.timer_state.remaining_seconds == 0 and not self.timer_state.alarm_triggered:
                self._trigger_alarm()

Explanation

  • Double condition prevents repeated triggers
  • remaining_seconds == 0 detects completion
  • not alarm_triggered ensures one-time execution
  • Delegates to dedicated alarm method

Why this matters

Without `alarm_triggered` check, alarm would fire every tick at zero. Delegation keeps tick loop clean.

This page will later include alarm state diagram.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Missing not before alarm_triggered
  • Using <= instead of ==
How to recover:
  • Use not self.timer_state.alarm_triggered
  • Use == 0 for exact check

What this page does

Records current timestamp for next tick's calculation.

Part: Configuration

Section: Time Engine

Depends on: Page 48 (alarm check complete)

This step is part of the larger process of timing continuity.

Code (this page)

python
self._last_tick_time = current_time

Explanation

  • Stores current time for next iteration
  • Must happen after all calculations
  • Only inside is_running block
  • Enables accurate elapsed on next tick

Why this matters

Without updating, elapsed would calculate from original start, causing exponential drift.

This page will later include timing update diagram.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Placing outside is_running block
  • Forgetting self. prefix
How to recover:
  • Ensure proper indentation
  • Use self._last_tick_time

What this page does

Triggers visual refresh after processing the tick.

Part: Configuration

Section: Time Engine

Depends on: Page 49 (timing complete)

This step is part of the larger process of UI synchronization.

Code (this page)

python
self._update_timer_display()

Explanation

  • Called every tick regardless of running state
  • Updates countdown, status, and colors
  • Placed outside is_running block (note indentation)
  • Ensures UI always reflects current state

Why this matters

Without calling display update, UI would freeze. Must update every tick for consistent visuals.

This page will later include tick β†’ display flow.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Wrong indentation (inside if)
  • Method name typo
How to recover:
  • Align with _tick method level
  • Use exact name: _update_timer_display

What this page does

Schedules the next tick loop execution.

Part: Configuration

Section: Time Engine

Depends on: Page 50 (display updated)

This step is part of the larger process of continuous timing.

Code (this page)

python
self.root.after(self.TICK_INTERVAL_MS, self._tick)

Explanation

  • root.after() schedules without blocking
  • TICK_INTERVAL_MS (200ms) for smooth updates
  • Method reschedules itself perpetually
  • At method level, outside all conditionals

Why this matters

Without rescheduling, tick runs once and stops. Using `after()` keeps UI responsive.

This page will later include scheduling diagram.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Placing inside conditional
  • Missing self. on method reference
How to recover:
  • Ensure at method level
  • Use self._tick reference

What this page does

Creates the method that synchronizes UI with timer state.

Part: Configuration

Section: UI Rendering

Depends on: Page 50 (called from tick)

This step is part of the larger process of visual feedback.

Code (this page)

python
def _update_timer_display(self):
        """Refresh countdown display and status."""

Explanation

  • Called every tick regardless of state
  • Updates countdown, status, colors, buttons
  • Centralizes all display logic
  • Separates rendering from timing

Why this matters

Without dedicated method, display logic would be duplicated. Centralizing ensures consistency.

This page will later include rendering flow diagram.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Method name doesn't match call
  • Missing self
How to recover:
  • Use exact name: _update_timer_display
  • Include self parameter

What this page does

Renders current remaining time in the countdown label.

Part: Configuration

Section: UI Rendering

Depends on: Page 52 (method exists), Page 36 (formatter exists)

This step is part of the larger process of countdown feedback.

Code (this page)

python
# Update countdown text
        self.countdown_label.config(
            text=self._format_time(self.timer_state.remaining_seconds)
        )

Explanation

  • Calls _format_time() to convert to MM:SS
  • Updates the large countdown label
  • Runs every tick for smooth updates
  • No conditionalβ€”always shows current state

Why this matters

Without updating, users wouldn't see time passing. This is the primary visual feedback.

This page will later include countdown update screenshot.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Wrong label name
  • Missing formatter call
How to recover:
  • Use self.countdown_label
  • Always format before display

What this page does

Applies "Time's Up" styling when alarm is active.

Part: Configuration

Section: UI Rendering β†’ Alarm State

Depends on: Page 53 (countdown updated)

This step is part of the larger process of completion feedback.

Code (this page)

python
# Handle alarm state visuals
        if self.timer_state.alarm_triggered:
            self.timer_frame.config(bg="#ffcccc")
            self.countdown_label.config(bg="#ffcccc")
            self.status_label.config(bg="#ffcccc", text="Time's Up!", fg="#cc0000")

Explanation

  • Light red background (#ffcccc) signals completion
  • Dark red text (#cc0000) emphasizes urgency
  • Status switches to "Time's Up!"
  • Uses alarm_triggered flag as gate

Why this matters

Without visual feedback, users might miss completion if sound muted. Red provides unmistakable indication.

This page will later include alarm state screenshot.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Invalid hex colors
  • Missing # prefix
How to recover:
  • Use valid hex: #ffcccc, #cc0000
  • Always prefix with #

What this page does

Restores default visuals when not in alarm state.

Part: Configuration

Section: UI Rendering β†’ Normal State

Depends on: Page 54 (alarm branch defined)

This step is part of the larger process of visual consistency.

Code (this page)

python
else:
            # Normal state visuals
            default_bg = self.root.cget("bg")
            self.timer_frame.config(bg=default_bg)
            self.countdown_label.config(bg=default_bg)
            self.status_label.config(bg=default_bg, fg="black")

Explanation

  • Clears alarm styling after reset
  • root.cget("bg") gets system default background
  • Restores black text for readability
  • Ensures visual matches logical state

Why this matters

Without restoring, red would persist forever. Using `cget` respects system theme.

This page will later include color restoration screenshot.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Forgetting to restore fg
  • Hardcoding background color
How to recover:
  • Set fg="black" in else
  • Use cget("bg") for native appearance

What this page does

Determines and applies the appropriate status message.

Part: Configuration

Section: UI Rendering β†’ Status Logic

Depends on: Page 55 (normal styling)

This step is part of the larger process of state communication.

Code (this page)

python
# Determine status text (uses settings.default_duration_seconds)
            if self.timer_state.is_running:
                self.status_label.config(text="Running")
            elif self.timer_state.remaining_seconds < self.settings.default_duration_seconds:
                self.status_label.config(text="Paused")
            else:
                self.status_label.config(text="Ready")

Explanation

  • "Running" when actively counting down
  • "Paused" when stopped with time elapsed (compares to Settings)
  • "Ready" when at configured default
  • Note: Uses self.settings.default_duration_seconds, not a constant

Why this matters

Without status, users must infer state. Using Settings for comparison ensures correct detection when duration changes.

This page will later include status state diagram.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Using hardcoded constant instead of settings
  • Wrong condition order
How to recover:
  • Use self.settings.default_duration_seconds
  • Check is_running first

What this page does

Enables or disables buttons based on timer state.

Part: Configuration

Section: UI Rendering

Depends on: Page 56 (status updated)

This step is part of the larger process of preventing invalid actions.

Code (this page)

python
# Update button states
        if self.timer_state.is_running:
            self.start_button.config(state=tk.DISABLED)
            self.stop_button.config(state=tk.NORMAL)
        else:
            self.start_button.config(state=tk.NORMAL)
            self.stop_button.config(state=tk.DISABLED)

Explanation

  • Running: disable Start, enable Stop
  • Not running: enable Start, disable Stop
  • Visual feedback matches logical constraints
  • Reset always enabled (not shown)

Why this matters

Without state management, users could click invalid buttons. Disabled state communicates what's allowed.

This page will later include button state screenshots.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Using string instead of constant
  • Wrong button names
How to recover:
  • Use tk.DISABLED and tk.NORMAL
  • Match button names from earlier pages

What this page does

Creates the method that handles countdown completion.

Part: Configuration

Section: Control Semantics

Depends on: Page 48 (called from tick)

This step is part of the larger process of completion handling.

Code (this page)

python
def _trigger_alarm(self):
        """Handle alarm when countdown reaches zero."""
        self.timer_state.alarm_triggered = True
        self.timer_state.is_running = False
        self._last_tick_time = None
        self._accumulated_time = 0.0

Explanation

  • alarm_triggered = True makes state sticky
  • is_running = False stops countdown
  • Clears timing variables for clean restart
  • State changes happen regardless of sound setting

Why this matters

Without sticky alarm, it could fire repeatedly. State must transition regardless of whether sound plays.

This page will later include alarm state machine.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Forgetting timing variable cleanup
  • Missing one of the state flags
How to recover:
  • Clear both timing variables
  • Set both state flags

What this page does

Plays the bell sound only if enabled in settings.

Part: Configuration

Section: Control Semantics

Depends on: Page 58 (state updated)

This step is part of the larger process of configurable alarm.

Code (this page)

python
# Respect alarm_enabled setting
        if self.settings.alarm_enabled:
            self.root.bell()

Explanation

  • Checks settings.alarm_enabled before playing
  • root.bell() plays system sound
  • Sound is optional; state changes are not
  • This is the core alarm toggle feature

Why this matters

Without this conditional, the checkbox would have no effect. This connects UI setting to actual behavior.

This page will later include alarm toggle behavior comparison.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Checking alarm_var instead of settings
  • Wrong indentation
How to recover:
  • Use self.settings.alarm_enabled
  • Ensure inside _trigger_alarm

What this page does

Creates the method that handles Start button clicks.

Part: Configuration

Section: Control Semantics

Depends on: Page 24 (button references this)

This step is part of the larger process of user control.

Code (this page)

python
def _on_start(self):
        """Handle Start button click."""
        if self.timer_state.is_running:
            return
        if self.timer_state.alarm_triggered:
            return
        if self.timer_state.remaining_seconds == 0:
            return

Explanation

  • is_running guard prevents double-start
  • alarm_triggered guard requires reset after completion
  • remaining_seconds == 0 guard prevents starting exhausted timer
  • Early returns keep code flat and readable

Why this matters

Without guards, users could cause undefined behavior. These enforce valid state transitions.

This page will later include guard decision tree.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Missing one of three guards
  • Wrong comparison
How to recover:
  • Include all three guards
  • Use separate if/return for each

What this page does

Begins countdown timing after passing all guards.

Part: Configuration

Section: Control Semantics

Depends on: Page 60 (guards defined)

This step is part of the larger process of running state transition.

Code (this page)

python
self.timer_state.is_running = True
        self._last_tick_time = time.monotonic()
        self._accumulated_time = 0.0

Explanation

  • is_running = True activates tick countdown logic
  • _last_tick_time establishes timing reference
  • _accumulated_time = 0.0 ensures clean start
  • Monotonic time prevents clock interference

Why this matters

Without setting `is_running`, tick would ignore countdown. Without timing setup, first second could be wrong.

This page will later include Ready β†’ Running diagram.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Using time.time() instead
  • Forgetting to clear accumulated
How to recover:
  • Use time.monotonic()
  • Set _accumulated_time = 0.0

What this page does

Creates the method that pauses the countdown.

Part: Configuration

Section: Control Semantics

Depends on: Page 25 (button references this)

This step is part of the larger process of pause functionality.

Code (this page)

python
def _on_stop(self):
        """Handle Stop button click."""
        if not self.timer_state.is_running:
            return

        self.timer_state.is_running = False
        self._last_tick_time = None
        self._accumulated_time = 0.0

Explanation

  • Guard: stopping stopped timer does nothing
  • is_running = False pauses countdown
  • Clears timing variables
  • Preserves remaining_seconds (true pause)

Why this matters

Without guard, stop could interfere. Without preserving remaining, pause would become reset.

This page will later include Running β†’ Paused diagram.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Missing guard
  • Resetting remaining_seconds
How to recover:
  • Check is_running before stopping
  • Only modify timing variables

What this page does

Creates the method that returns timer to ready state.

Part: Configuration

Section: Control Semantics

Depends on: Page 26 (button references this)

This step is part of the larger process of state reset.

Code (this page)

python
def _on_reset(self):
        """Handle Reset button click."""
        self.timer_state.is_running = False
        self.timer_state.alarm_triggered = False
        self.timer_state.remaining_seconds = self.settings.default_duration_seconds
        self._last_tick_time = None
        self._accumulated_time = 0.0

Explanation

  • Stops any running countdown
  • Clears sticky alarm flag
  • Restores configured default duration (from Settings)
  • Clears all timing bookkeeping
  • Safe to call from any state

Why this matters

Without reset, app restart needed after alarm. Using `settings.default_duration_seconds` respects configuration.

This page will later include Any State β†’ Ready diagram.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Using hardcoded duration
  • Forgetting alarm_triggered
How to recover:
  • Use self.settings.default_duration_seconds
  • Clear all five values

What this page does

Creates the Apply button handler that commits configuration changes.

Part: Configuration

Section: Apply Workflow

Depends on: Page 35 (button references this)

This step is part of the larger process of configuration commit.

Code (this page)

python
def _on_apply(self):
        """Handle Apply Settings button click."""

Explanation

  • Apply is the explicit commit point
  • No auto-apply behavior
  • Handler validates, updates, and provides feedback
  • Docstring clarifies purpose

Why this matters

Without handler, Apply button does nothing. This is the coordinator for all configuration changes.

This page will later include apply workflow flowchart.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Name doesn't match button command
  • Missing self
How to recover:
  • Use exact name: _on_apply
  • Include self parameter

What this page does

Checks input and shows error if invalid.

Part: Configuration

Section: Apply Workflow

Depends on: Page 64 (handler exists), Page 37 (parser exists)

This step is part of the larger process of validation gating.

Code (this page)

python
# Validate duration
        duration_seconds = self._parse_duration_input()
        if duration_seconds is None:
            messagebox.showerror(
                "Invalid Input",
                "Duration must be a number between 1 and 999 minutes."
            )
            return

Explanation

  • Single validation through _parse_duration_input()
  • None triggers error dialog
  • Clear message with valid range
  • Early return prevents bad settings

Why this matters

Without validation, invalid values could corrupt settings. Error dialog provides immediate feedback.

This page will later include error dialog screenshot.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Missing messagebox import
  • Wrong comparison
How to recover:
  • Ensure messagebox imported
  • Use is None for comparison

What this page does

Stores current default for safe timer sync decision.

Part: Configuration

Section: Apply Workflow

Depends on: Page 65 (validation passed)

This step is part of the larger process of protecting user progress.

Code (this page)

python
# Capture old default for smart sync
        old_default = self.settings.default_duration_seconds

Explanation

  • Needed to detect if timer is still at old default
  • Supports safe update without affecting paused countdown
  • Must capture before settings change
  • Simple snapshot of current value

Why this matters

Without old value, couldn't distinguish Ready (safe to update) from Paused (unsafe). This protects user progress.

This page will later include old vs new comparison diagram.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Capturing after update
  • Wrong attribute
How to recover:
  • Place before settings modification
  • Use self.settings.default_duration_seconds

What this page does

Commits validated duration into Settings.

Part: Configuration

Section: Apply Workflow

Depends on: Page 66 (old value captured)

This step is part of the larger process of applying changes.

Code (this page)

python
# Update settings
        self.settings.default_duration_seconds = duration_seconds

Explanation

  • Duration from validated parse (already seconds)
  • Settings becomes runtime source of truth
  • Reset and status depend on this
  • Single assignment point

Why this matters

Without update, validated input would be lost. This is the actual "apply" for duration.

This page will later include settings update flow.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Using minutes instead of seconds
  • Wrong attribute
How to recover:
  • Use duration_seconds (already converted)
  • Use default_duration_seconds

What this page does

Commits alarm toggle state into Settings.

Part: Configuration

Section: Apply Workflow

Depends on: Page 67 (duration updated)

This step is part of the larger process of applying changes.

Code (this page)

python
self.settings.alarm_enabled = self.alarm_var.get()

Explanation

  • .get() extracts boolean from BooleanVar
  • Settings stores authoritative value
  • Alarm behavior reads from Settings
  • Separates UI state from runtime state

Why this matters

Without update, checkbox change wouldn't affect behavior. This connects UI to runtime.

This page will later include checkbox β†’ settings flow.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Forgetting .get()
  • Wrong variable
How to recover:
  • Always call .get() on Tkinter vars
  • Use self.alarm_var

What this page does

Determines if it's safe to update countdown to new default.

Part: Configuration

Section: Apply Workflow

Depends on: Page 68 (settings updated), Page 66 (old_default exists)

This step is part of the larger process of protecting progress.

Code (this page)

python
# Smart sync: update timer only if in Ready state
        if (not self.timer_state.is_running and
            not self.timer_state.alarm_triggered and
            self.timer_state.remaining_seconds == old_default):

Explanation

  • not is_running: don't interrupt active countdown
  • not alarm_triggered: don't reset after completion
  • remaining == old_default: only if still at Ready
  • All three required for safe update

Why this matters

Without guards, Apply would overwrite paused session. Three conditions ensure only Ready timers update.

This page will later include sync decision tree.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Missing condition
  • Using new settings instead of old
How to recover:
  • Include all three guards
  • Compare against old_default

What this page does

Updates countdown when safe conditions met.

Part: Configuration

Section: Apply Workflow

Depends on: Page 69 (condition checked)

This step is part of the larger process of syncing config to timer.

Code (this page)

python
self.timer_state.remaining_seconds = self.settings.default_duration_seconds

Explanation

  • Only executes if all conditions passed
  • Updates countdown to new default
  • Keeps UI consistent after Apply
  • Display updates on next tick

Why this matters

Without update, users would need Reset after every Apply. Smart sync improves experience while protecting active sessions.

This page will later include before/after screenshots.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Wrong indentation
  • Using old_default instead of new
How to recover:
  • Ensure inside if block
  • Use self.settings.default_duration_seconds

What this page does

Confirms Apply succeeded with info dialog.

Part: Configuration

Section: Apply Workflow

Depends on: Page 70 (sync complete)

This step is part of the larger process of user feedback.

Code (this page)

python
# Confirm success
        messagebox.showinfo("Settings", "Settings applied!")

Explanation

  • Confirms change was accepted
  • Uses info dialog (not error) for success
  • Brief, clear message
  • Completes Apply workflow

Why this matters

Without confirmation, users wouldn't know if Apply worked. Provides closure to the action.

This page will later include success dialog screenshot.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Using showerror instead
  • Wrong import
How to recover:
  • Use messagebox.showinfo
  • Ensure messagebox imported

What this page does

Creates the startup function that launches the application.

Part: Configuration

Section: Entry Point

Depends on: All previous pages

This step is part of the larger process of making the app runnable.

Code (this page)

python
def main():
    root = tk.Tk()
    app = StudyTimerApp(root)
    root.mainloop()

Explanation

  • Creates Tk root window
  • Instantiates application controller
  • Starts event loop (blocks until close)
  • Encapsulates complete startup

Why this matters

Without main function, startup would be at module level. This encapsulates the three-step launch.

This page will later include startup sequence diagram.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Forgetting mainloop()
  • Wrong order
How to recover:
  • Always end with mainloop()
  • Create root, then app, then loop

What this page does

Ensures app runs only when executed directly.

Part: Configuration

Section: Entry Point

Depends on: Page 72 (main exists)

This step is part of the larger process of module reusability.

Code (this page)

python
if __name__ == "__main__":
    main()

Explanation

  • Allows importing without auto-launch
  • Standard Python script pattern
  • Keeps module reusable
  • Calls main only on direct execution

Why this matters

Without guard, importing for testing would launch GUI. This is Python best practice.

This page will later include import vs execution diagram.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

⚠ If something breaks here

Common issues at this step:
  • Typo in __name__ or "__main__"
  • Forgetting to call main()
How to recover:
  • Use double underscores
  • Include parentheses: main()

What this page does

Provides the complete code for verification.

Part: Configuration

Section: Verification

Depends on: All previous pages

This step is part of the larger process of ensuring correctness.

Code (this page)

python
# Complete Configuration code should match this structure:
# - 5 imports (tkinter, messagebox, time, Optional, dataclass)
# - Settings dataclass with 2 fields
# - TimerState class with 3 attributes
# - StudyTimerApp class with all methods
# - main() function and entry guard

Explanation

Why this matters

This checkpoint ensures all code is correct before proceeding. Every feature should work.

βœ“ Checkpoint

After this page, you should be able to:

If this is not true, stop and review before continuing.

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Explanation

Why this matters

What this page does

Confirms the Configuration stage is fully complete.

Explanation

Why this matters

Contents