このドキュメントでは、ポモドーロタイマーのアーキテクチャ設計、技術的決定、実装パターンについて説明します。
- Architecture Overview
- Design Decisions
- Module Specifications
- Data Flow
- State Machine
- Implementation Patterns
- Philosophy Alignment
ポモドーロタイマーは、レイヤー化されたアーキテクチャを採用しています:
┌─────────────────────────────────────────────┐
│ Streamlit UI Layer │
│ (app.py / components/) │
└─────────────┬───────────────────────────────┘
│
│ Uses
↓
┌─────────────────────────────────────────────┐
│ Business Logic Layer │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Timer │ │ Stats │ │ Notify │ │
│ │ (timer.py)│ │(stats.py)│ │(notif.py)│ │
│ └────┬─────┘ └────┬─────┘ └──────────┘ │
│ │ │ │
│ │ │ │
│ ↓ ↓ │
│ ┌─────────────────────────────┐ │
│ │ Storage Layer │ │
│ │ (storage.py) │ │
│ └──────────┬──────────────────┘ │
└─────────────┼──────────────────────────────┘
│
↓
┌───────────────┐
│ sessions.json │
│ (data/) │
└───────────────┘
- レイヤー分離: UI、ビジネスロジック、データアクセスを明確に分離
- 単方向データフロー: UIがビジネスロジックを呼び出し、結果を表示
- 疎結合: 各モジュールは明確なインターフェースを通じてのみ通信
- テスタビリティ: 各レイヤーを独立してテスト可能
決定: Streamlitを採用
理由:
- 開発速度: 1-2時間で完成可能(学習プロジェクトに最適)
- 技術スタックの統一: Pythonのみ、JavaScriptの知識不要
- 組み込みリアクティビティ: 状態管理が簡単
- 十分な機能性: タイマーアプリケーションに必要な機能を提供
トレードオフ:
- アニメーション制御が限定的(受け入れ可能)
- リフレッシュベースの更新(タイマーには十分)
決定: JSONファイルストレージ
理由:
- シンプルさ: セットアップ不要、依存関係なし
- 人間が読める: デバッグとバックアップが容易
- 適切なスケール: 1日10-20セッション程度では十分高速
- プライバシー: ローカル保存、外部サービス不要
トレードオフ:
- 複雑なクエリには非効率(必要なし)
- 並行書き込みの制限(単一ユーザーアプリで問題なし)
決定: ターゲット時刻ベースのタイマー
問題: time.sleep()の累積使用はドリフトを引き起こす
解決策:
# ドリフトなしのタイマー実装
end_time = time.time() + duration_seconds
while time.time() < end_time:
remaining = int(end_time - time.time())
update_display(remaining)
time.sleep(0.1) # 頻繁にチェック利点:
- 長時間実行でも正確
- システムクロックに基づく
- 予測可能な動作
決定: plyerライブラリによるOS-native通知
理由:
- クロスプラットフォーム: Windows、macOS、Linux対応
- ネイティブUX: OSの通知設定を尊重
- シンプルなAPI: 数行のコードで実装可能
代替案を却下:
- ブラウザ通知: Streamlitとの統合が複雑
- カスタム通知: 車輪の再発明、メンテナンス負担
Purpose: 中核となるデータモデルを定義
Dependencies: なし(純粋なデータ)
Exports:
@dataclass
class Session:
"""完了したポモドーロセッションを表す"""
id: str
type: str # "work", "short_break", "long_break"
start_time: datetime
end_time: datetime
completed: bool
duration_seconds: int
@dataclass
class TimerConfig:
"""タイマー設定"""
work_duration: int = 1500
short_break: int = 300
long_break: int = 900
pomodoros_until_long_break: int = 4
sound_enabled: bool = True
notification_enabled: bool = TrueDesign Notes:
- データクラスを使用して簡潔性を実現
- デフォルト値はクラシックなポモドーロ設定
- 不変性(可能な限り)
Purpose: JSONファイルのI/O処理
Dependencies: models.py
Exports:
def save_session(session: Session) -> None:
"""セッションを即座に保存(増分処理パターン)"""
def load_sessions(
start_date: date | None = None,
end_date: date | None = None
) -> list[Session]:
"""日付範囲でフィルタリングしてセッションを読み込む"""
def get_sessions_today() -> list[Session]:
"""今日のセッションを取得"""Implementation Pattern:
def save_session(session: Session):
"""アトミックライトでデータ破損を防止"""
sessions = load_all_sessions()
sessions.append(session)
# 一時ファイルに書き込み、次にリネーム(アトミック)
temp_path = DATA_PATH.with_suffix('.json.tmp')
with open(temp_path, 'w') as f:
json.dump([asdict(s) for s in sessions], f, indent=2)
temp_path.rename(DATA_PATH)Design Notes:
- 増分保存(セッション完了ごと)
- アトミックライト(データ破損防止)
- 遅延読み込み(起動時のオーバーヘッドなし)
Purpose: タイマー状態とカウントダウンロジックを管理
Dependencies: models.py, storage.py, notifications.py
Exports:
class TimerState(Enum):
IDLE = "idle"
WORKING = "working"
SHORT_BREAK = "short_break"
LONG_BREAK = "long_break"
PAUSED = "paused"
class PomodoroTimer:
def start(self) -> None
def pause(self) -> int # 残り秒数を返す
def stop(self) -> None
def get_state(self) -> TimerState
def get_remaining(self) -> int
def is_complete(self) -> boolState Transitions:
IDLE → WORKING (start)
WORKING → PAUSED (pause)
PAUSED → WORKING (resume)
WORKING → SHORT_BREAK (完了、ポモドーロ < 4)
WORKING → LONG_BREAK (完了、ポモドーロ = 4)
SHORT_BREAK → WORKING (完了)
LONG_BREAK → WORKING (完了)
Any → IDLE (stop)
Design Notes:
- 明示的な状態マシン
- ログ付きの状態遷移
- ドリフトなしのタイマー実装
- セッション完了時の自動保存
Purpose: セッションデータから統計を計算
Dependencies: models.py, storage.py
Exports:
def calculate_today_stats() -> dict:
"""今日の集中時間と完了ポモドーロ数を返す"""
return {
"focus_time": int, # 分単位
"completed_pomodoros": int
}
def calculate_streak() -> int:
"""少なくとも1ポモドーロを完了した連続日数"""
def calculate_weekly_overview() -> dict[str, int]:
"""過去7日間の日別ポモドーロ数"""
return {
"2025-01-15": 8,
"2025-01-16": 6,
# ...
}Design Notes:
- 遅延計算(キャッシュなし - データが小規模)
- 日付ベースのフィルタリング
- 単純な集計(複雑な分析なし)
Purpose: OS通知をトリガー
Dependencies: なし
Exports:
def notify_session_complete(session_type: str) -> None:
"""セッション完了通知を表示"""
def notify_break_complete() -> None:
"""休憩完了通知を表示"""Implementation:
from plyer import notification
def notify_session_complete(session_type: str):
if session_type == "work":
title = "ポモドーロ完了!"
message = "よくできました!5分休憩しましょう。"
# ...
notification.notify(
title=title,
message=message,
app_name="Pomodoro Timer",
timeout=10
)Design Notes:
- シンプルなラッパー
- OS設定を尊重
- 設定可能な有効化/無効化
Purpose: Streamlitメインアプリケーション
Dependencies: すべてのビジネスロジックモジュール
Structure:
def main():
# セッション状態の初期化
initialize_session_state()
# タイマー表示
render_timer_display()
# コントロールボタン
render_controls()
# 統計表示
render_statistics()
# 設定パネル
render_settings()Design Notes:
- セッション状態によるタイマー状態の永続化
- コンポーネントベースのUI構造
- キーボードショートカットのサポート
1. Timer reaches zero
↓
2. timer.py: is_complete() returns True
↓
3. timer.py: Create Session object
↓
4. storage.py: save_session(session)
↓
5. notifications.py: notify_session_complete()
↓
6. timer.py: Transition to next state
↓
7. ui/app.py: Update display
1. User navigates to stats view
↓
2. ui/app.py: Call stats functions
↓
3. stats.py: Load sessions from storage
↓
4. stats.py: Calculate metrics
↓
5. ui/app.py: Render statistics
┌─────────────────────────────────────────────┐
│ │
│ IDLE │
│ │
└──────────────┬──────────────────────────────┘
│ start()
↓
┌─────────────────────────────────────────────┐
│ │
│ WORKING │
│ (25 minutes) │
│ │
└──────┬───────────────────────┬──────────────┘
│ pause() │ complete
↓ ↓
┌──────────────┐ ┌──────────────────┐
│ │ │ SHORT_BREAK or │
│ PAUSED │ │ LONG_BREAK │
│ │ │ (5 or 15 min) │
└──────┬───────┘ └────────┬─────────┘
│ resume() │ complete
│ ↓
└────────────────> WORKING
or IDLE
- IDLE → WORKING:
start()で新しい作業セッション開始 - WORKING → PAUSED:
pause()で残り時間を保持 - PAUSED → WORKING:
resume()で残り時間から再開 - WORKING → SHORT_BREAK: 完了、ポモドーロカウント < 4
- WORKING → LONG_BREAK: 完了、ポモドーロカウント = 4
- SHORT_BREAK → WORKING: 休憩完了、作業を再開
- LONG_BREAK → WORKING: 長休憩完了、カウントリセット
- Any → IDLE:
stop()でリセット
セッション完了ごとに保存(バッチ処理なし):
def on_session_complete(session: Session):
# 即座に保存
storage.save_session(session)
# 中断しても安全理由:
- データ損失の防止
- 処理が常にボトルネック(ディスクI/Oではない)
- ユーザーはいつでも終了可能
データ破損を防止:
# 一時ファイルに書き込み
temp_path = path.with_suffix('.tmp')
write_data(temp_path)
# アトミックリネーム
temp_path.rename(path)理由:
- クラッシュ中の破損なし
- POSIXシステムでアトミックなリネーム
- 既存データの保護
システムクロックベースのタイマー:
end_time = time.time() + duration
while time.time() < end_time:
remaining = end_time - time.time()
update(remaining)
time.sleep(0.1)理由:
sleep()の累積エラーなし- 長時間実行で正確
- 予測可能な完了時刻
ログ付きの状態変更:
def transition_to(new_state: TimerState):
logger.info(f"Timer: {self.state} → {new_state}")
self.state = new_state理由:
- デバッグ可能性
- 監査証跡
- 状態変化の可視性
適用箇所:
- JSONストレージ(データベースなし)
- Streamlit UI(フロントエンドフレームワークなし)
- 基本統計のみ(高度な分析なし)
- ローカルのみ(クラウド同期なし)
回避したもの:
- 複雑な状態管理ライブラリ
- カスタムアニメーションエンジン
- マイクロサービスアーキテクチャ
- 将来の仮説的機能
Bricks(モジュール):
- 各モジュールは独立して再生成可能
- 明確な境界と責任
- 最小限の依存関係
Studs(インターフェース):
Timer.start/pause/stopStorage.save/loadStats.calculate_*Notifications.notify_*
再生成可能性: 各モジュールは以下から完全に再構築可能:
- このドキュメントの仕様
- モジュールのdocstring
- テストスイート
実装内容:
- TODOコメントなし
- プレースホルダーなし
- すべてのコードが動作する
- すべての例がコピー&ペースト可能
検証方法:
- 各モジュールが独立して実行可能
- テストが完全に通過
- ドキュメントの例が実際に動作
アクセシビリティ:
- 7:1コントラスト比(タイマーテキスト)
- キーボードナビゲーション
- スクリーンリーダー対応
- 設定可能な通知
ユーザビリティ:
- 明確な状態表示
- 大きなタッチターゲット
- 予測可能な動作
- 役立つエラーメッセージ
- タイマー更新頻度: 10Hz(0.1秒間隔)- 滑らかな表示
- UI応答性: ボタンクリックが100ms以内
- セッション保存時間: 50ms未満(1000+セッションでも)
- 統計計算: 30日間のデータで200ms未満
- アプリ起動時間: 1秒未満
- 遅延読み込み: 必要になるまでセッションを読み込まない
- 増分保存: バッチ処理ではなくセッションごと
- シンプルな計算: キャッシュなし(データが小規模)
- 効率的なループ: タイマーで0.1秒のスリープ
各モジュールを独立してテスト:
- models.py: データ検証、シリアライゼーション
- storage.py: 保存/読み込み、フィルタリング、エラー処理
- timer.py: 状態遷移、時間計算
- stats.py: 計算の正確性
モジュール間の相互作用:
- timer + storage: セッション完了時の自動保存
- timer + notifications: 完了時の通知トリガー
- stats + storage: 保存されたデータからの統計
実際の使用シナリオ:
- タイマーの精度(25分が実際に25分か)
- 通知の配信(OS通知が表示されるか)
- UI応答性(スムーズな更新か)
現在の設計で対応可能:
- カスタム期間の設定
- 追加の統計メトリクス
- テーマのカスタマイズ
- エクスポート機能
アーキテクチャ変更が必要:
- クラウド同期(ストレージ抽象化が必要)
- マルチユーザー(認証と承認が必要)
- リアルタイムコラボレーション(WebSocketsが必要)
意図的に除外:
- タスク管理統合
- チーム機能
- 高度な分析
- モバイルアプリ
Created: 2025-11-22 Last Updated: 2025-11-22 Status: Active Specification Related Documents: README.md, AGENTS.md, plan.md