# New Idle Popup Functionality - Python Implementation

## Overview

This document describes the new idle popup functionality implemented in the Python backend. The system detects consecutive idle intervals and manages popup display, server upload blocking, and auto-stop after 60 minutes of unhandled idle time.

## Requirements

### Core Requirements

1. **Normal Operation**: If user works (activity interval != 0), everything runs normally
2. **Idle Detection**: If minimum 2 consecutive idle intervals are found, popup must show on frontend
3. **Block Server Uploads**: Until popup is not handled, all activity intervals are considered as idle and NOT sent to server
4. **Repeatable Cycles**: If user works → idle (handled) → works again → becomes idle again (2 consecutive idle intervals) → need to display idle popup again. This should repeat.
5. **60-Minute Auto-Stop**: If idle popup occurs and is not handled by user under 60 minutes, app should stop and consider all idle intervals as "unkeep" conditions

## Implementation Details

### State Variables

```python
self._idle_popup_active: bool = False  # Track if idle popup is currently active
self._idle_popup_triggered_at: Optional[datetime.datetime] = None  # When popup was triggered
self._idle_popup_timeout_seconds: int = 3600  # 60 minutes timeout
self._api_pause_active: bool = False  # Block server uploads while popup active
```

### Flow Diagram

```
┌─────────────────────────────────────────────────────────────┐
│                    Normal Operation                         │
│  User works (activity != 0) → Intervals sent to server      │
└─────────────────────────────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────┐
│              Idle Detection (Every Interval)                │
│  Check last 2 consecutive intervals for zero activity       │
└─────────────────────────────────────────────────────────────┘
                            │
                            ▼
                    ┌───────────────┐
                    │ 2 Consecutive │
                    │  Idle Found?  │
                    └───────┬───────┘
                            │
                ┌───────────┴───────────┐
                │                       │
               YES                     NO
                │                       │
                ▼                       ▼
    ┌──────────────────────┐   ┌──────────────┐
    │ Trigger Idle Popup   │   │ Continue     │
    │ - Set _idle_popup_  │   │ Normal Flow │
    │   active = True      │   │              │
    │ - Block server       │   └──────────────┘
    │   uploads            │
    │ - Mark intervals     │
    │   as is_idle=True    │
    └──────────┬───────────┘
               │
               ▼
    ┌──────────────────────┐
    │  Popup Active State  │
    │  - All new intervals │
    │    marked as idle    │
    │  - Server uploads    │
    │    blocked           │
    └──────────┬───────────┘
               │
    ┌──────────┴──────────┐
    │                     │
    ▼                     ▼
┌──────────┐      ┌──────────────┐
│ User     │      │ 60 Minutes  │
│ Handles  │      │ Timeout?     │
│ Popup?   │      └──────┬───────┘
└────┬─────┘             │
     │                   │
     │                   YES
     │                   │
     │                   ▼
     │          ┌─────────────────┐
     │          │ Auto-Stop Timer │
     │          │ - Mark all idle │
     │          │   as unkeep     │
     │          │ - Stop tracking │
     │          └─────────────────┘
     │
     ▼
┌──────────────────────┐
│ Clear Popup State    │
│ - _idle_popup_active │
│   = False            │
│ - Resume server      │
│   uploads            │
│ - Continue normal    │
│   operation          │
└──────────────────────┘
```

## Key Methods

### 1. `check_idle_detection()`

Checks for 2 consecutive idle intervals.

**Logic:**
- Get last 2 regular intervals (exclude breaks)
- Check if both have zero activity (keyboard=0, mouse=0, average=0)
- Return True if 2 consecutive idle intervals found

**Logging:**
```
[IdleDetection] Starting idle detection check (counter={counter})
[IdleDetection] Checking {count} intervals for consecutive idle: interval_ids={ids}
[IdleDetection] Interval {idx}/{total} (id={id}): start={start}, end={end}, activity=[keyboard={kb}%, mouse={mouse}%, average={avg}%], is_zero_activity={bool}
[IdleDetection] ✓ IDLE DETECTED: 2 consecutive regular intervals with zero activity (counter={counter}, interval_ids={ids})
```

### 2. `arise_idle_popup()`

Triggers idle popup and manages state.

**Logic:**
- Check if break session is active (skip if yes)
- Check 60-minute timeout (if exceeded, auto-stop)
- **No cooldown**: Python sends trigger every time 2+ consecutive idle intervals detected
- Send popup trigger to Electron
- Set `_idle_popup_active = True`
- Block server uploads (`_api_pause_active = True`)

**Logging:**
```
[IdlePopup] arise_idle_popup() called - checking conditions before sending trigger to Electron
[IdlePopup] Triggering popup: consecutive_idle_count={count}, timestamp={timestamp}
[IdlePopup] ✓ Trigger sent to Electron (2 consecutive zero-activity intervals) [attempt {n}]
[IdlePopup] State: active=True, triggered_at={timestamp}, timeout_limit={3600}s
[IdlePopup] Server uploads BLOCKED until popup handled
```

### 3. `check_idle_popup_timeout()`

Checks if 60 minutes have passed since popup was triggered.

**Logic:**
- If `_idle_popup_active` is True
- Calculate time since `_idle_popup_triggered_at`
- If >= 3600 seconds (60 minutes):
  - Auto-stop timer
  - Mark all idle intervals as unkeep (is_uploaded=True)
  - Clear popup state
  - Stop tracking

**Logging:**
```
[IdleTimeout] Checking timeout: elapsed={seconds}s, limit={3600}s
[IdleTimeout] ⚠ TIMEOUT REACHED: {elapsed}s >= {3600}s - Auto-stopping timer
[IdleTimeout] Marking {count} idle intervals as unkeep (is_uploaded=True): interval_ids={ids}
[IdleTimeout] Timer stopped due to unhandled idle popup (60 minutes timeout)
[IdleTimeout] Sent auto-stop signal to Electron
```

### 4. `_should_pause_api_calls()`

Determines if server uploads should be blocked.

**Logic:**
- If `_idle_popup_active` is True → return True (block uploads)
- If all intervals have zero activity → return True (block uploads)
- Otherwise → return False (allow uploads)

**Logging:**
```
[APIBlock] Pausing uploads: Idle popup is active (triggered at {timestamp})
[APIBlock] Pausing uploads: All {count} intervals have zero activity
[APIBlock] Allowing uploads: Intervals have activity or popup not active
```

### 5. `handle_idle_popup_response()`

Handles user response to idle popup.

**CRITICAL**: The first popup response (keep/unkeep/stop/continue) applies to **all** consecutive idle intervals in the current session. Because Python sends triggers every interval (no cooldown) and the frontend shows only one popup, this single response handles all accumulated idle intervals.

**Logic:**
- If `keep_idle_time=True`: Keep intervals with is_uploaded=False
- If `keep_idle_time=False`: Mark intervals as is_uploaded=True (unkeep)
- Clear `_idle_popup_active = False`
- Resume server uploads (`_api_pause_active = False`)

**Logging:**
```
[IdleResponse] Handling idle popup response: keep_idle_time={bool}, action={action}, project_id={id}, task_id={id}
[IdleResponse] Clearing popup state and resuming normal operation
[IdleResponse] ✓ State cleared - API calls will resume, intervals will be sent to server
```

## Interval Marking Logic

### While Popup Active

All new intervals are marked as `is_idle=True` and `is_uploaded=False`:

```python
if self._idle_popup_active and self._idle_popup_triggered_at is not None:
    has_zero_activity = await self._has_zero_activity_for_interval(start_date, end_date)
    is_idle = self._idle_popup_active and has_zero_activity
    # Save with is_idle=True, is_uploaded=False
```

**Logging:**
```
[IdleInterval] Regular interval (in range) - break_id={id}, is_break={bool}, status={bool}
[IdleInterval] Saving: break_id={id}, is_idle={bool} (popup_is_active={bool}, has_zero_activity={bool})
[IdleInterval] ActivityLevel: Keyboard={kb}%, Mouse={mouse}%, Average={avg}%
[IdleInterval] ⚠ Interval marked as IDLE - will NOT be sent to server until popup handled
```

### After Popup Handled

- If `keep_idle_time=True`: Intervals remain with `is_uploaded=False` (will be sent to server)
- If `keep_idle_time=False`: Intervals marked as `is_uploaded=True` (won't be sent to server)

### After 60-Minute Timeout

All idle intervals marked as `is_uploaded=True` (unkeep condition).

## Server Upload Blocking

### Implementation

In `__push_activity_log()`:

```python
# Check 60-minute timeout first
timeout_reached = await self.check_idle_popup_timeout()
if timeout_reached:
    logger.warning("[IdleTimeout] Auto-stop triggered - stopping activity tracking")
    break  # Stop the main loop

# Check if API calls should be paused
should_pause_api = await self._should_pause_api_calls(intervals)
if should_pause_api:
    logger.info("[APIBlock] API calls paused - skipping push_activity_logs")
    self._api_pause_active = True
    await precise_sleep(self.interval, stop_event)
    continue  # Skip server upload
```

### When Blocked

- `_idle_popup_active = True` → All uploads blocked
- All intervals with zero activity → Uploads blocked
- Intervals are saved to database but not sent to server

## Repeatable Cycles

### Scenario: Work → Idle → Work → Idle

1. **First Idle Period:**
   - User works (intervals sent to server)
   - 2 consecutive idle intervals detected
   - Popup shown, uploads blocked
   - User handles popup → state cleared

2. **User Works Again:**
   - Normal operation resumes
   - Intervals sent to server normally

3. **Second Idle Period:**
   - 2 consecutive idle intervals detected again
   - Popup shown again (same logic)
   - Process repeats

### Key Points

- Each idle period is independent
- Popup state is cleared after handling
- System can detect new idle periods after user activity
- No cooldown: Python sends trigger every interval; frontend suppresses duplicate popups

## No Cooldown + Frontend Suppression (per-session handling)

### Design

1. **Python (no cooldown)**: Sends `idle_popup_trigger` every time 2+ consecutive idle intervals are detected. No 5‑minute cooldown; each new idle interval can trigger a send.

2. **Frontend (suppress duplicates)**: When `showIdlePopup` is called and `isIdlePopupVisible` is already true, the frontend:
   - Does not show another popup (keeps the existing one)
   - Updates the idle timer display with the latest `remainingIntervals` (so "You are idle from last X" stays correct)

3. **First popup response handles all**: The user’s single response (keep/unkeep/stop/continue) applies to **all** consecutive idle intervals in that session. Each Python trigger calls `_mark_previous_zero_activity_intervals_as_idle`, so by the time the user responds, all consecutive idle intervals are marked as `is_idle=1`. `handle_idle_popup_response` then operates on all such intervals (via `_mark_idle_intervals_as_uploaded` or `sync_activity_immediately`).

### Flow Summary

| Step | Event |
|------|-------|
| 1 | Python detects 2 consecutive idle → sends trigger |
| 2 | Frontend shows popup (first time) |
| 3 | Python keeps detecting idle → sends triggers every interval |
| 4 | Frontend ignores new triggers (popup already visible), updates idle display |
| 5 | Each Python trigger marks more intervals as `is_idle=1` |
| 6 | User clicks Continue/Stop/Reassign once → one response sent |
| 7 | Python’s `handle_idle_popup_response` processes **all** consecutive idle intervals |

## 60-Minute Auto-Stop

### Trigger Condition

- `_idle_popup_active = True`
- Time since `_idle_popup_triggered_at` >= 3600 seconds

### Actions

1. Mark all idle intervals as `is_uploaded=True` (unkeep)
2. Clear popup state
3. Stop timer tracking (break main loop)
4. Send stop signal to Electron

### Logging

```
[IdleTimeout] Checking timeout: elapsed={seconds}s, limit={3600}s
[IdleTimeout] ⚠ TIMEOUT REACHED: {elapsed}s >= {3600}s - Auto-stopping timer
[IdleTimeout] Marking {count} idle intervals as unkeep (is_uploaded=True): interval_ids={ids}
[IdleTimeout] Timer stopped due to unhandled idle popup (60 minutes timeout)
[IdleTimeout] Sent auto-stop signal to Electron
```

## Logging Standards

All idle-related operations use consistent logging prefixes:

- `[IdleDetection]` - Idle detection checks
- `[IdlePopup]` - Popup trigger and state management
- `[IdleResponse]` - User response handling
- `[IdleTimeout]` - 60-minute timeout checks
- `[APIBlock]` - Server upload blocking
- `[IdleInterval]` - Interval marking as idle

### Log Levels

- `INFO` - Normal operations, state changes
- `WARNING` - Timeout reached, unusual conditions
- `ERROR` - Exceptions, failures
- `DEBUG` - Detailed state information

## Example Log Flow

```
[IdleDetection] Starting idle detection check (counter=13)
[IdleDetection] Checking 2 intervals for consecutive idle: interval_ids=[64, 63]
[IdleDetection] Interval 1/2 (id=64): start=2026-02-20 11:45:00, end=2026-02-20 11:50:00, activity=[keyboard=0.0%, mouse=0.0%, average=0.0%], is_zero_activity=True
[IdleDetection] Interval 2/2 (id=63): start=2026-02-20 11:40:00, end=2026-02-20 11:45:00, activity=[keyboard=0.0%, mouse=0.0%, average=0.0%], is_zero_activity=True
[IdleDetection] ✓ IDLE DETECTED: 2 consecutive regular intervals with zero activity (counter=13, interval_ids=[64, 63])
[IdlePopup] arise_idle_popup() called - checking conditions before sending trigger to Electron
[IdlePopup] Triggering popup: consecutive_idle_count=2, timestamp=2026-02-20 11:50:05.406000
[IdlePopup] ✓ Trigger sent to Electron (2 consecutive zero-activity intervals) [attempt 1]
[IdlePopup] State: active=True, triggered_at=2026-02-20 11:50:05.406000, timeout_limit=3600s
[IdlePopup] Server uploads BLOCKED until popup handled
[APIBlock] Pausing uploads: Idle popup is active (triggered at 2026-02-20 11:50:05.406000)
[APIBlock] API calls paused - skipping push_activity_logs (zero activity intervals or idle popup active)
[APIBlock] Intervals will be saved to database but NOT sent to server while popup active
[IdleInterval] Regular interval (in range) - break_id=0, is_break=0, status=True
[IdleInterval] Saving: break_id=0, is_idle=True (popup_is_active=True, has_zero_activity=True)
[IdleInterval] ActivityLevel: Keyboard=0.0%, Mouse=0.0%, Average=0.0%
[IdleInterval] ⚠ Interval marked as IDLE - will NOT be sent to server until popup handled
[IdleResponse] Handling idle popup response: keep_idle_time=True, action=continue
[IdleResponse] Clearing popup state and resuming normal operation
[IdleResponse] ✓ State cleared - API calls will resume, intervals will be sent to server
```

## Testing Checklist

- [ ] Normal operation: Intervals with activity sent to server
- [ ] Idle detection: 2 consecutive idle intervals trigger popup
- [ ] Server blocking: No uploads while popup active
- [ ] Interval marking: All intervals marked as idle while popup active
- [ ] Popup handling: State cleared after user response
- [ ] Repeat cycles: Work → Idle → Work → Idle works correctly
- [ ] 60-minute timeout: Auto-stop triggers after 60 minutes
- [ ] Unkeep marking: Idle intervals marked as unkeep after timeout
- [ ] Activity detection: Mouse/keyboard activity clears popup state

## Error Handling

- IPC connection failures: Retry with exponential backoff
- Database errors: Log and continue, don't crash
- Timeout calculation: Handle None values gracefully
- State inconsistencies: Reset state if detected

## Implementation Summary

### Files Modified

1. **`activity_controller.py`**:
   - Added `_idle_popup_timeout_seconds` state variable (3600 seconds = 60 minutes)
   - Enhanced `check_idle_detection()` with comprehensive logging
   - Added `check_idle_popup_timeout()` method for 60-minute auto-stop
   - Enhanced `arise_idle_popup()` with timeout check and better logging
   - **Removed cooldown**: Python sends trigger every time 2+ consecutive idle intervals detected
   - Enhanced `handle_idle_popup_response()` with docstring clarifying first response handles all
   - Simplified `handle_idle_popup_shown()` (no-op, cooldown removed)

2. **`workstatus-app-electronjs/src/renderer/js/timer.js`**:
   - **Frontend suppression**: At start of `showIdlePopup()`, if `isIdlePopupVisible` is true, suppress duplicate (return early)
   - **Update idle display**: When suppressing, still update `idleStartTime` and `updateIdleTimer()` so "You are idle from last X" reflects latest count

### Key Changes

1. **Idle Detection**:
   - Checks for 2 consecutive idle intervals
   - Comprehensive logging with `[IdleDetection]` prefix
   - Detailed activity level logging for each interval

2. **Popup Management**:
   - Triggers popup when 2 consecutive idle intervals found
   - **No cooldown**: Sends trigger every interval; frontend suppresses duplicate popups
   - Blocks server uploads while popup active
   - Checks 60-minute timeout before triggering new popup
   - Logs all state changes with `[IdlePopup]` prefix

2a. **Frontend Suppression** (timer.js):
   - If popup already visible, ignore new triggers (first popup handles all)
   - Update idle timer display when suppressing (so "You are idle from last X" stays accurate)

3. **Server Upload Blocking**:
   - `_should_pause_api_calls()` returns True if popup active
   - All intervals saved to database but NOT sent to server
   - Logs with `[APIBlock]` prefix

4. **60-Minute Timeout**:
   - `check_idle_popup_timeout()` checks elapsed time
   - Auto-stops timer if 60 minutes elapsed
   - Marks all idle intervals as unkeep (is_uploaded=True)
   - Logs with `[IdleTimeout]` prefix

5. **Interval Marking**:
   - While popup active, all intervals with zero activity marked as `is_idle=True`
   - Logs with `[IdleInterval]` prefix
   - Clear indication when intervals won't be sent to server

6. **Response Handling**:
   - **First popup response handles all**: keep/unkeep/stop/continue applies to ALL consecutive idle intervals in the session
   - Clears popup state after user response
   - Resumes server uploads
   - Logs with `[IdleResponse]` prefix

## Future Enhancements

- Configurable timeout (not fixed 60 minutes)
- Multiple timeout thresholds (warning at 30 min, stop at 60 min)
- Idle period statistics tracking
- User notification before auto-stop
