Skip to content

Instantly share code, notes, and snippets.

@drillan
Created November 22, 2025 13:35
Show Gist options
  • Select an option

  • Save drillan/ec9e5237897c46afd8a92f5007a7aa09 to your computer and use it in GitHub Desktop.

Select an option

Save drillan/ec9e5237897c46afd8a92f5007a7aa09 to your computer and use it in GitHub Desktop.
ポモドーロタイマープロジェクトの DESIGN.md - Amplifier(AI駆動開発ツール)で生成

Pomodoro Timer - Design Document

このドキュメントでは、ポモドーロタイマーのアーキテクチャ設計、技術的決定、実装パターンについて説明します。

Table of Contents


Architecture Overview

System Architecture

ポモドーロタイマーは、レイヤー化されたアーキテクチャを採用しています:

┌─────────────────────────────────────────────┐
│           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/)       │
      └───────────────┘

Key Principles

  1. レイヤー分離: UI、ビジネスロジック、データアクセスを明確に分離
  2. 単方向データフロー: UIがビジネスロジックを呼び出し、結果を表示
  3. 疎結合: 各モジュールは明確なインターフェースを通じてのみ通信
  4. テスタビリティ: 各レイヤーを独立してテスト可能

Design Decisions

1. Streamlit vs HTML/CSS/JavaScript

決定: Streamlitを採用

理由:

  • 開発速度: 1-2時間で完成可能(学習プロジェクトに最適)
  • 技術スタックの統一: Pythonのみ、JavaScriptの知識不要
  • 組み込みリアクティビティ: 状態管理が簡単
  • 十分な機能性: タイマーアプリケーションに必要な機能を提供

トレードオフ:

  • アニメーション制御が限定的(受け入れ可能)
  • リフレッシュベースの更新(タイマーには十分)

2. JSON Storage vs Database

決定: JSONファイルストレージ

理由:

  • シンプルさ: セットアップ不要、依存関係なし
  • 人間が読める: デバッグとバックアップが容易
  • 適切なスケール: 1日10-20セッション程度では十分高速
  • プライバシー: ローカル保存、外部サービス不要

トレードオフ:

  • 複雑なクエリには非効率(必要なし)
  • 並行書き込みの制限(単一ユーザーアプリで問題なし)

3. Timer Implementation Strategy

決定: ターゲット時刻ベースのタイマー

問題: 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)  # 頻繁にチェック

利点:

  • 長時間実行でも正確
  • システムクロックに基づく
  • 予測可能な動作

4. Notification System

決定: plyerライブラリによるOS-native通知

理由:

  • クロスプラットフォーム: Windows、macOS、Linux対応
  • ネイティブUX: OSの通知設定を尊重
  • シンプルなAPI: 数行のコードで実装可能

代替案を却下:

  • ブラウザ通知: Streamlitとの統合が複雑
  • カスタム通知: 車輪の再発明、メンテナンス負担

Module Specifications

models.py - Data Structures

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 = True

Design Notes:

  • データクラスを使用して簡潔性を実現
  • デフォルト値はクラシックなポモドーロ設定
  • 不変性(可能な限り)

storage.py - Data Persistence

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:

  • 増分保存(セッション完了ごと)
  • アトミックライト(データ破損防止)
  • 遅延読み込み(起動時のオーバーヘッドなし)

timer.py - Core Logic

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) -> bool

State 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:

  • 明示的な状態マシン
  • ログ付きの状態遷移
  • ドリフトなしのタイマー実装
  • セッション完了時の自動保存

stats.py - Statistics

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:

  • 遅延計算(キャッシュなし - データが小規模)
  • 日付ベースのフィルタリング
  • 単純な集計(複雑な分析なし)

notifications.py - System Integration

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設定を尊重
  • 設定可能な有効化/無効化

ui/app.py - Main Application

Purpose: Streamlitメインアプリケーション

Dependencies: すべてのビジネスロジックモジュール

Structure:

def main():
    # セッション状態の初期化
    initialize_session_state()

    # タイマー表示
    render_timer_display()

    # コントロールボタン
    render_controls()

    # 統計表示
    render_statistics()

    # 設定パネル
    render_settings()

Design Notes:

  • セッション状態によるタイマー状態の永続化
  • コンポーネントベースのUI構造
  • キーボードショートカットのサポート

Data Flow

Session Completion Flow

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

Statistics Display Flow

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

State Machine

Timer States

┌─────────────────────────────────────────────┐
│                                             │
│                   IDLE                      │
│                                             │
└──────────────┬──────────────────────────────┘
               │ start()
               ↓
┌─────────────────────────────────────────────┐
│                                             │
│                 WORKING                     │
│              (25 minutes)                   │
│                                             │
└──────┬───────────────────────┬──────────────┘
       │ pause()               │ complete
       ↓                       ↓
┌──────────────┐    ┌──────────────────┐
│              │    │  SHORT_BREAK or  │
│    PAUSED    │    │   LONG_BREAK     │
│              │    │   (5 or 15 min)  │
└──────┬───────┘    └────────┬─────────┘
       │ resume()            │ complete
       │                     ↓
       └────────────────> WORKING
                            or IDLE

State Transition Rules

  1. IDLE → WORKING: start()で新しい作業セッション開始
  2. WORKING → PAUSED: pause()で残り時間を保持
  3. PAUSED → WORKING: resume()で残り時間から再開
  4. WORKING → SHORT_BREAK: 完了、ポモドーロカウント < 4
  5. WORKING → LONG_BREAK: 完了、ポモドーロカウント = 4
  6. SHORT_BREAK → WORKING: 休憩完了、作業を再開
  7. LONG_BREAK → WORKING: 長休憩完了、カウントリセット
  8. Any → IDLE: stop()でリセット

Implementation Patterns

1. Incremental Processing Pattern

セッション完了ごとに保存(バッチ処理なし):

def on_session_complete(session: Session):
    # 即座に保存
    storage.save_session(session)
    # 中断しても安全

理由:

  • データ損失の防止
  • 処理が常にボトルネック(ディスクI/Oではない)
  • ユーザーはいつでも終了可能

2. Atomic Write Pattern

データ破損を防止:

# 一時ファイルに書き込み
temp_path = path.with_suffix('.tmp')
write_data(temp_path)
# アトミックリネーム
temp_path.rename(path)

理由:

  • クラッシュ中の破損なし
  • POSIXシステムでアトミックなリネーム
  • 既存データの保護

3. Drift-Free Timer Pattern

システムクロックベースのタイマー:

end_time = time.time() + duration
while time.time() < end_time:
    remaining = end_time - time.time()
    update(remaining)
    time.sleep(0.1)

理由:

  • sleep()の累積エラーなし
  • 長時間実行で正確
  • 予測可能な完了時刻

4. Explicit State Transitions

ログ付きの状態変更:

def transition_to(new_state: TimerState):
    logger.info(f"Timer: {self.state}{new_state}")
    self.state = new_state

理由:

  • デバッグ可能性
  • 監査証跡
  • 状態変化の可視性

Philosophy Alignment

Ruthless Simplicity

適用箇所:

  • JSONストレージ(データベースなし)
  • Streamlit UI(フロントエンドフレームワークなし)
  • 基本統計のみ(高度な分析なし)
  • ローカルのみ(クラウド同期なし)

回避したもの:

  • 複雑な状態管理ライブラリ
  • カスタムアニメーションエンジン
  • マイクロサービスアーキテクチャ
  • 将来の仮説的機能

Modular Design (Bricks & Studs)

Bricks(モジュール):

  • 各モジュールは独立して再生成可能
  • 明確な境界と責任
  • 最小限の依存関係

Studs(インターフェース):

  • Timer.start/pause/stop
  • Storage.save/load
  • Stats.calculate_*
  • Notifications.notify_*

再生成可能性: 各モジュールは以下から完全に再構築可能:

  • このドキュメントの仕様
  • モジュールのdocstring
  • テストスイート

Zero-BS Principle

実装内容:

  • TODOコメントなし
  • プレースホルダーなし
  • すべてのコードが動作する
  • すべての例がコピー&ペースト可能

検証方法:

  • 各モジュールが独立して実行可能
  • テストが完全に通過
  • ドキュメントの例が実際に動作

Design for Humans

アクセシビリティ:

  • 7:1コントラスト比(タイマーテキスト)
  • キーボードナビゲーション
  • スクリーンリーダー対応
  • 設定可能な通知

ユーザビリティ:

  • 明確な状態表示
  • 大きなタッチターゲット
  • 予測可能な動作
  • 役立つエラーメッセージ

Performance Considerations

Target Metrics

  • タイマー更新頻度: 10Hz(0.1秒間隔)- 滑らかな表示
  • UI応答性: ボタンクリックが100ms以内
  • セッション保存時間: 50ms未満(1000+セッションでも)
  • 統計計算: 30日間のデータで200ms未満
  • アプリ起動時間: 1秒未満

Optimization Strategies

  1. 遅延読み込み: 必要になるまでセッションを読み込まない
  2. 増分保存: バッチ処理ではなくセッションごと
  3. シンプルな計算: キャッシュなし(データが小規模)
  4. 効率的なループ: タイマーで0.1秒のスリープ

Testing Strategy

Unit Tests (60%)

各モジュールを独立してテスト:

  • models.py: データ検証、シリアライゼーション
  • storage.py: 保存/読み込み、フィルタリング、エラー処理
  • timer.py: 状態遷移、時間計算
  • stats.py: 計算の正確性

Integration Tests (30%)

モジュール間の相互作用:

  • timer + storage: セッション完了時の自動保存
  • timer + notifications: 完了時の通知トリガー
  • stats + storage: 保存されたデータからの統計

Manual Tests (10%)

実際の使用シナリオ:

  • タイマーの精度(25分が実際に25分か)
  • 通知の配信(OS通知が表示されるか)
  • UI応答性(スムーズな更新か)

Future Considerations

現在の設計で対応可能:

  • カスタム期間の設定
  • 追加の統計メトリクス
  • テーマのカスタマイズ
  • エクスポート機能

アーキテクチャ変更が必要:

  • クラウド同期(ストレージ抽象化が必要)
  • マルチユーザー(認証と承認が必要)
  • リアルタイムコラボレーション(WebSocketsが必要)

意図的に除外:

  • タスク管理統合
  • チーム機能
  • 高度な分析
  • モバイルアプリ

Document Metadata

Created: 2025-11-22 Last Updated: 2025-11-22 Status: Active Specification Related Documents: README.md, AGENTS.md, plan.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment