13 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
Deskmeter is a productivity tool that measures time spent across desktop workspaces. It consists of four main components:
- dmcore: Core tracking daemon (
dmapp/dmcore/main.py) that monitors active workspace and task changes
Architecture
Core Components
Task Management System:
- Task definitions stored in files, tracked via
dmapp/dmcore/task.py - File modification time monitoring for automatic task switching
- Hierarchical task organization under workspace paths (e.g.,
work/default,work/dlt)
Workspace Tracking:
- Uses
wmctrlto detect active X11 workspace (requires XORG, not Wayland) - Maps workspace indices to labels in
dmapp/dmcore/main.py:desktops - Special handling for "work" desktops with automatic task enforcement
Data Storage:
- MongoDB backend storing workspace switches with timestamps and durations
- State persistence in
dmapp/dmcore/state.pyfor current workspace/task tracking
Web Interface Structure
Flask Routes (dmapp/dmweb/dm.py):
/- Today's productivity summary (large display with auto-refresh)/calendar/<scope>/<year>/<month>/<day>?grid=<1|3|6>- Google Calendar-style view with aggregated task blocks- Scopes:
daily,weekly,monthly - Grid: Hour aggregation (1h, 3h, or 6h blocks)
- Shows only active workspaces (Plan/Think/Work) with gaps for idle time
- Cascading overlapping blocks for multiple tasks per time period
- Scopes:
/switches/<scope>/<year>/<month>/<day>- Raw switches view with proportional cell heights- Shows all workspace switches with time-based height visualization
/work- Work projects breakdown for today/totals- All-time statistics/day/<month>/<day>- Single day view/period/<start>/<end>- Custom date range/api/current_task- JSON endpoint for current task info (used by GNOME extension)/api/today- HTML fragment for AJAX updates
Development Commands
Python Backend
# Install dependencies
pip install -r requirements.txt
# Start MongoDB service
sudo systemctl start mongod.service
# Run core tracking daemon
cd dmapp/dmcore
python3 main.py
# Run Flask web server
cd dmapp/dmweb
python3 run.py
# Run tests
cd dmapp/tests
python3 test_dmapp.py
Running the Web Interface
Use the dmweb.sh script for flexible deployment:
cd /home/mariano/wdir/run
# Syntax: ./dmweb.sh <worktree> <port> <debug>
./dmweb.sh dm-fend-updates 10002 1
# Parameters:
# - worktree: directory name in /home/mariano/wdir/ (default: dm)
# - port: Flask server port (default: 10000)
# - debug: 1 for debug mode with auto-reload, 0 for production (default: 0)
Angular Frontend (Not Yet Implemented)
The Angular frontend (dmapp/dmfnt/) mentioned in the architecture is planned but not currently implemented. All current functionality is in the Flask templates.
GNOME Extension
cd gnome-extension
# Install extension
./install.sh
# Restart GNOME Shell (X11 only)
# Alt+F2 → type 'r' → Enter
# (On Wayland: log out and back in)
# Enable extension
gnome-extensions enable deskmeter-indicator@local
# Update extension after changes
./update.sh
# Then restart GNOME Shell
# Debug logs
journalctl -f -o cat /usr/bin/gnome-shell
# Test API endpoint
curl http://localhost:10000/api/current_task
System Setup
The application requires:
- MongoDB running locally
- wmctrl installed (
apt install wmctrl) - X11 desktop environment (not Wayland for dmcore; GNOME extension works on both X11/Wayland)
- Python virtual environment recommended
Key Configuration
Workspace Labels: Edit desktops tuple in dmapp/dmcore/main.py:12
Work Desktop Mapping: Configure work_desktops dict in dmapp/dmcore/main.py:13
Timezone: Set in dmapp/dmcore/main.py:19 using zoneinfo
Calendar & Switches Visualization
Overview
Two complementary views for analyzing workspace switching patterns:
- Calendar View (
/calendar) - Google Calendar-style blocks showing when you worked on each task - Switches View (
/switches) - Granular list of every workspace switch
Both support daily, weekly, and monthly scopes with navigation.
Calendar View Features
Visual Design:
- Task blocks positioned at actual times during the day
- Each task gets consistent color (HSL hue based on task name hash)
- Gradient within each block:
- Left side (bright) = active time (Plan/Think/Work workspaces)
- Right side (faded) = idle time (Other/Away workspaces)
- Gradient split point = active/idle ratio
Block Grouping:
- Consecutive switches to same task are merged into blocks
- Minimum durations filter micro-switches:
- Daily: 60 seconds
- Weekly: 5 minutes
- Monthly: 10 minutes
Data Structure (get_task_blocks_calendar()):
{
'task_id': str,
'task_path': str,
'start': datetime,
'end': datetime,
'duration': int, # total seconds
'active_seconds': int, # Plan/Think/Work time
'idle_seconds': int, # Other/Away time
'active_ratio': float # 0.0 to 1.0
}
Switches View Features
Visual Design:
- Each switch row color-coded by task (same colors as calendar)
- Border color and background both use task color for visual grouping
- Active workspaces (Plan/Think/Work) shown bold
- Idle workspaces (Other/Away) shown faded
Display:
- Timestamp, workspace name, task path, duration for each switch
- Stats summary showing total switches and total time
- All switches in chronological order
Task History System
Problem: Historic task IDs (from archived task files) display as raw IDs instead of paths.
Solution: Automatic on-demand task resolution via task_history collection:
- When displaying unknown task ID,
get_task_path()tries:- Current
taskscollection (active tasks frommainfile) task_historycollection (cached historic tasks)- File scan: searches all files in
/home/mariano/LETRAS/adm/task/
- Current
- When found via file scan, stores in
task_historyfor next time - Returns task_id as fallback if not found anywhere
Benefits:
- No manual history loading required
- No dmcore startup overhead
- History populates automatically as you view old data
- Frontend self-sufficient and independent
Files:
- Frontend parsing:
dmapp/dmweb/get_period_times.py:parse_task_line() - File scanning:
dmapp/dmweb/get_period_times.py:load_task_from_files() - Resolution:
dmapp/dmweb/get_period_times.py:get_task_path()
Component Integration
How Task Updates Flow
- User switches workspace → GNOME detects immediately
- dmcore polling (every 2 seconds) → detects workspace change via
wmctrl - Task enforcement → dmcore updates MongoDB state collection with current task
- GNOME extension → listens to
workspace-switchedsignal, waits 2.2s, queries API - Panel indicator updates → shows current task path in top panel
GNOME Extension Architecture
Location: gnome-extension/deskmeter-indicator@local/
Files:
extension.js- Main extension logic, workspace switch listener, API pollingmetadata.json- Extension metadata (GNOME 40-47 compatible)stylesheet.css- Panel indicator styling
Behavior:
- Connects to GNOME's
workspace-switchedsignal - Debounces updates by 2.2 seconds after workspace switch
- Queries
/api/current_taskendpoint - Displays task path in panel, truncates if > 40 chars
- Shows last 2 path segments for long paths (e.g.,
.../work/project)
Update Workflow:
- Edit source files in
gnome-extension/deskmeter-indicator@local/ - Run
./update.shto copy to~/.local/share/gnome-shell/extensions/ - Restart GNOME Shell (
Alt+F2→ron X11, or log out/in on Wayland) - Check logs:
journalctl -f -o cat /usr/bin/gnome-shell
Development Notes
- The system enforces task constraints within work desktops - switching to a non-work task automatically reverts to the designated work task
- Task files are monitored for changes to enable automatic context switching
- Web interface runs on port 10000 by default (configurable via
dmweb.shscript) - Core daemon sleeps for 2 seconds between workspace checks
- GNOME extension debounce delay (2.2s) must be > dmcore polling interval (2s) to ensure MongoDB is updated before API query
Working on Calendar/Switches Views
Current Implementation
Routes (dmapp/dmweb/dm.py):
/calendarand/switchesmust be defined BEFORE the catch-all/<string:task>route- Both support
/<scope>(daily/weekly/monthly) and/<scope>/<year>/<month>/<day> - Navigation via prev/next date links and tab switching between scopes
Templates:
calendar_view.html- 24-hour grid with task blocks positioned by timeswitches_view.html- Simple list with task-based color coding- Both use Jinja2
|hashfilter for color generation (registered in__init__.py)
Data Flow:
- Route extracts date range from URL params
- Calls
get_task_blocks_calendar()orget_raw_switches() - Each function calls
get_task_path()for task name resolution get_task_path()auto-populatestask_historyon cache miss- Template renders with inline styles (HSL colors from task name hash)
Potential Improvements
- Performance: Cache
get_task_path()results in-memory to avoid repeated DB lookups - Color collision: Current hash % 360 can produce similar hues for different tasks
- Weekly/monthly calendar: Currently shows sequential blocks, could show multi-column grid like daily view
- Task filtering: Add URL param to filter by specific task(s)
- Export: Add CSV/JSON export of block or switch data
- Overlapping blocks: If switching between tasks within same hour, blocks could overlap visually
Key Design Decisions
- No JavaScript frameworks - Pure server-rendered HTML with minimal CSS transitions
- Task history is frontend-only - dmcore never touches task_history collection
- Color coding consistency - Same task always gets same color across all views
- Gradient indicates work quality - Visual distinction between active focus and idle time
- Multiple thresholds - Different minimum block durations for different scopes reduces noise
UI/UX Design
Dark Mode Theme (applied across all views):
- Background:
#1a1a1a - Text:
#e0e0e0 - Accent/links:
#6b9bd1 - Borders:
#444 - Component backgrounds:
#2a2a2a
Calendar View Features:
- Hour-based aggregation (configurable: 1h, 3h, 6h grids)
- Overlapping task blocks cascade right and down (8% horizontal, 4px vertical per task)
- Only active workspaces shown (Plan/Think/Work) - idle time appears as gaps
- Task colors generated via hash-based HSL for consistency
- Height proportional to time spent in each grid period
Switches View Features:
- Cell heights proportional to switch duration (30px min, 200px max)
- Color-coded by task with hash-based HSL
- Dark backgrounds with reduced opacity for idle workspaces
Navigation:
- Consistent nav bar across all views: Today | Calendar | Switches | Work | All Time
- All views interconnected for easy movement between different data perspectives
GNOME Extension
A GNOME Shell extension displays the current task in the top panel:
- Location:
gnome-extension/deskmeter-indicator@local/ - Polls
/api/current_taskendpoint - Updates on workspace switch with 2.2s debounce
- Automatically truncates long task paths
- Installation:
./gnome-extension/install.shor./gnome-extension/update.sh
Key Implementation Details
Calendar Aggregation (dmapp/dmweb/get_period_times.py:get_task_blocks_calendar()):
- Aggregates switches by configurable time grid (1h, 3h, or 6h)
- Only includes active workspaces: Plan, Think, Work
- Returns blocks with task_id, task_path, start time, duration, and hour
- Filters out blocks shorter than
min_block_seconds
Task Path Resolution:
- Primary:
taskscollection (current tasks) - Fallback:
task_historycollection (cached historical tasks) - Last resort: On-demand file search in task directory
- Caching prevents repeated file I/O
Switch Height Calculation (switches view):
- Finds max delta in current view
- Heights scale linearly:
base_height + (ratio * (max_height - base_height)) - Base: 30px, Max: 200px
Tool Development Guidelines
- This is a personal tool, not professionally developed
- Reuse existing code as much as possible unless refactor is required explicitly
- Only modify existing code if it absolutely doesn't make sense to add a new flow
- When adding features, maintain the dark mode theme consistency
- All new views should include the standard navigation bar