- Frontend: Vue 3 (Composition API) + TypeScript
- Styling: Tailwind CSS v4 (CSS-first approach)
- Build Tool: Vite
- State Management: Composable (useFlowmodoro.ts)
src/
├── components/ # Atomic components
│ ├── AppFooter.vue
│ ├── AppHeader.vue
│ ├── BreakBankDisplay.vue
│ ├── ControlButtons.vue
│ ├── InfoModal.vue
│ ├── SessionHistory.vue
│ └── TimerDisplay.vue
├── composables/
│ └── useFlowmodoro.ts # Business logic (State Machine)
├── App.vue # Main Layout
├── main.ts # Entry point
└── style.css # Tailwind imports
Critical Technical Rule: Time is calculated using Date.now() deltas, NOT with setInterval.
Reason: Browsers "sleep" inactive tabs, making setInterval unreliable.
Implementation:
// FLOW Mode (Counting UP)
const tick = () => {
const now = Date.now();
sessionElapsed = now - startTime; // Delta calculation
requestAnimationFrame(tick);
};
// BREAK Mode (Counting DOWN)
const tick = () => {
const now = Date.now();
const delta = now - lastTickTime;
breakBank = Math.max(0, breakBank - delta);
lastTickTime = now;
requestAnimationFrame(tick);
};Implicit Rule: Use requestAnimationFrame instead of setInterval for better precision and performance.
Responsibilities:
- Manage application state (FlowMode Enum, sessionElapsed, breakBank)
- Implement timing mechanism (Date.now() deltas)
- Execute atomic "Register Break" transaction
- Calculate Daily Total and maintain history
Public API:
{
// State
mode: Ref<FlowMode>, // Enum: IDLE, FLOW, BREAK
sessionElapsed: Ref<number>, // ms
breakBank: Ref<number>, // ms
dailyTotal: ComputedRef<number>, // ms
history: Ref<Session[]>,
isRunning: ComputedRef<boolean>,
// Actions
startFlow: () => void,
stopFlowAndBank: () => void, // Atomic transaction
stopBreak: () => void,
pause: () => void,
// Utilities
formatTime: (ms: number) => { minutes: string, seconds: string },
formatHours: (ms: number) => string,
// Dev Only
addFiveMinutes: () => void, // [DEV] Testing helper
// Type Export
FlowMode // Enum Access
}Single Responsibility: useFlowmodoro.ts only handles timing logic, App.vue only handles UI
Open/Closed: Break ratio (breakRatio = 5) is configurable without modifying core logic
No God Objects: Composable exposes only what is necessary
Explicit Typing: Strict TypeScript to avoid runtime errors
Null Coalescing: Use of ?? for clean default values
No localStorage persistence (not yet necessary)
No authentication (single-user app)
No ratio configuration (5:1 is the standard)