trackWork 0.14.2

A terminal-based time tracking application for managing work sessions
- use cargo check for testing the build
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

when using the Explore tool. primarily use the architecture.md files as the basis to find things.

## Architecture Documents

Architecture `.md` files document specific subsystem designs, data flows, and extension patterns. **Always read the relevant architecture doc before working on a feature.** If an architecture doc is outdated or missing for the area you are about to modify, create or update it first before starting implementation work.

| Area | File | Covers |
|------|------|--------|
| Integrations & Secrets | [`src/integrations/architecture.md`](src/integrations/architecture.md) | IntegrationKind dispatch, SecretsManager, Jira API, settings field layout, adding new integrations |
| Cursor & Text Input | [`src/cursor.architecture.md`](src/cursor.architecture.md) | Cursor movement, insert/delete at position, blinking render, UTF-8 safety, adding cursor to new modes |
| Timeline | [`src/dashboard/drawers/timeline/architecture.md`](src/dashboard/drawers/timeline/architecture.md) | View window vs actual times, resolution calculation, row rendering, overlap handling, header stats |
| Weekly Summary | [`src/dashboard/week_summary/architecture.md`](src/dashboard/week_summary/architecture.md) | Full-screen 7-day side-by-side view, shared vertical scale, per-day metrics (logged/unlogged/workday), missing-lunch warning |
| Terminal Colors | [`src/termcolor.architecture.md`](src/termcolor.architecture.md) | OSC 4 palette query, theme-aware color resolution (`themed_rgb`, `dim_toward_bg`, `breathe_color`), never hardcode grays |

## Project Overview

trackWork is a terminal-based time tracking application built with Rust. It features a TUI (Terminal User Interface) for managing work sessions with support for JIRA integration, running timers, and weekly statistics.

## Changelogs

Before updating `APP_CHANGELOG.md` or `CHANGELOG.md` (or bumping `Cargo.toml` version), READ [`references/changelog.md`](references/changelog.md).

## Build and Run Commands

```bash
# Build the project
cargo build

# Build release version
cargo build --release
./build-release.sh  # Uses build script if available

# Run the application
cargo run --release

# Run in development mode
cargo run

# Check for compilation errors
cargo check

# Run tests (if any)
cargo test

# Format code
cargo fmt

# Lint code
cargo clippy
```

## Architecture

The codebase follows a clean separation of concerns with distinct layers:

### Module Structure

- **main.rs**: Application entry point and event loop. Handles terminal setup/teardown, event polling, and keyboard input routing based on current `InputMode`.
- **app.rs**: Core application state and business logic. Contains the `App` struct which manages entries, dates, input modes, and user interactions.
- **ui.rs**: Terminal UI rendering layer. Completely separated from business logic - handles all drawing operations including timeline visualization, entry lists, weekly stats, and settings screens.
- **db.rs**: SQLite database operations. Handles CRUD operations for time entries, reordering, and statistics queries.
- **models.rs**: Data models (`TimeEntry`) with methods for duration calculations and color selection logic.
- **config.rs**: Configuration management using TOML files stored at `~/.timetrack.toml`. Handles integration selection, JIRA settings, and color palette.
- **cursor.rs**: Text cursor logic for all input fields. Handles cursor movement, character insertion/deletion at cursor position, and blinking cursor rendering. See `src/cursor.architecture.md`.
- **integrations/**: Pluggable integration system for work logging and issue opening. Contains `IntegrationKind` enum, secrets management, and Jira Cloud API integration. See `src/integrations/architecture.md`.
- **settings/**: Settings screen UI rendering (`ui.rs`) and keyboard input handling (`input.rs`). Field layout is dynamic based on active integration.
- **dashboard/**: Main dashboard UI and input routing across all modes (Normal, Editing, Creating, etc.).

### Key Design Patterns

**InputMode State Machine**: The app uses an enum `InputMode` to represent different UI states (Normal, Editing, Creating, ConfirmDelete, Config, Settings). Each mode has its own keyboard bindings and UI rendering logic.

**Operations Menu (`m`)**: `InputMode::OperationsMenu { selected_index }` modal. Items (count = `OPERATIONS_MENU_ITEMS` in `dashboard/input.rs`): 0=Sync task names, 1=Weekly summary, 2=Keyboard shortcuts, 3=Settings. Item 2 opens `InputMode::Hotkeys`, a read-only modal (`draw_hotkeys` in `dashboard/main_element/ui.rs`) listing extra shortcuts not on the main help bar (e.g. `Shift+L` mark-logged, `t` Tasks, reorder keys). Any key closes it.

**Color Management**: Entries are auto-assigned colors based on a smart algorithm that minimizes repetition. The algorithm tracks color usage and applies penalties to recently used colors, selecting from the least-used options. Users can customize the 6-color palette via settings.

**Highlight Contrast**: The selection highlight uses `bg(Color::DarkGray)`. Any text styled with `Color::DarkGray` foreground becomes invisible when its row is highlighted. Secondary/muted text must switch to `Color::Gray` (or lighter) when the row is selected. See the two-row task name rendering in `dashboard/main_element/ui.rs` for the pattern.

**Running Timers**: Entries with `end_time = None` are considered "running". The UI updates in real-time (100ms polling) to show elapsed time. Press 's' to stop a running entry.

**Date Navigation**: The app shows entries for a single date at a time. Use arrow keys (←/→) to navigate between days. All queries are date-scoped.

**Entry Ordering**: Entries have a `display_order` field that persists their visual position. Use Ctrl+↑/↓ to reorder entries, which updates this field in the database.

**Integration System**: Users choose between `CustomCommands` (shell-based, fully configurable) and `Jira` (built-in Jira Cloud REST API). The active integration is selected in Settings (field 0). When `Jira` is active, pressing 'l' POSTs a worklog directly via the API; when `CustomCommands` is active, it runs the user-defined shell command. The integration kind is stored in `config.integration` and API tokens are stored via `SecretsManager`.

**Off Work Entries**: `off_work` bool flag on entries (lunch/personal). Create w/ `f` in Normal mode (`App::start_creating_off_work` → Creating mode with `off_work: true`; same flow as `n`/new). Excluded from daily total + weekly stats. Renders magenta `( off work )` tag in `dashboard/main_element/ui.rs`. Timeline shows them as a light-gray hatched `▒` bar.

**At Work Row**: per-day presence span shown as a pinned bordered row above the entry list (`draw_at_work_row` in `dashboard/main_element/ui.rs`). Span = first task start → last task end (live; updates whenever a task ends). Selectable via Up from entry 0 (`app.at_work_selected`); when selected `s`=clock-in-now (toggle start override), `e`=edit start/end (`InputMode::EditingDay`), `d`/`D`=reset to auto. Manual overrides persist per-date in `day_entries` table (`day_start_override`/`day_end_override`, NULL=auto). Effective span computed by `App::at_work_span()`.

**Event Triggers**: Settings → "Triggers" button opens the `Triggers` InputMode sub-screen (`src/triggers/`). Per-event webhooks (`day_start`, `ooo_start`, `ooo_end`) POST a templated JSON body on lifecycle events. Config in `Config::triggers`; fired via `App::fire_trigger()`. See [`src/integrations/architecture.md`](src/integrations/architecture.md) "Event Triggers".

**Cursor System**: All text input fields (Creating, Editing, EditingDay, Triggers, Settings) have a navigable cursor. The cursor position (`cursor_pos: usize`) is stored in each `InputMode` variant. Left/Right arrow keys move the cursor, characters insert at cursor position, backspace deletes before cursor. The cursor renders as a blinking `|` (500ms cycle). All cursor logic lives in `cursor.rs` to keep `app.rs` lean.

## Database Schema

The SQLite database (`~/.timetrack.db`) contains a single table:

```sql
CREATE TABLE time_entries (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    description TEXT NOT NULL,
    start_time TEXT NOT NULL,          -- Format: "YYYY-MM-DD HH:MM:SS"
    end_time TEXT,                     -- NULL for running timers
    display_order INTEGER NOT NULL,    -- For manual reordering
    color INTEGER NOT NULL DEFAULT 0,  -- 0-5 index into color palette
    issue_key TEXT DEFAULT '',         -- Optional JIRA issue key
    created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
```

`time_entries` also has `logged INTEGER` and `off_work INTEGER` columns (migrated in). A separate `day_entries` table holds per-day At Work overrides:

```sql
CREATE TABLE day_entries (
    date TEXT PRIMARY KEY,   -- "YYYY-MM-DD"
    start_override TEXT,     -- NULL = derive from first task
    end_override TEXT        -- NULL = derive from last task end
);
```

The database layer automatically handles schema migrations for new columns.

## Event Loop Architecture

The main event loop (in `main.rs:run_app`) follows this pattern:

1. Draw UI with current app state
2. Poll for keyboard events with 100ms timeout (allows running timer updates)
3. Route events to appropriate handler based on `InputMode`
4. Update app state
5. Repeat

This design keeps the UI responsive and allows real-time updates for running timers without blocking input.

## Configuration

- **Database**: `~/.timetrack.db` (SQLite)
- **Config file**: `~/.timetrack.toml` (TOML format)
- **Secrets**: Keyring (service `"trackwork"`) with file fallback at `~/.timetrack.secrets` (TOML, chmod 600)
- **Config structure**: `integration` (CustomCommands/Jira) + shell commands + Jira URL/email + date format + legacy time format + 6-color palette

## Weekly Statistics

The weekly stats panel (right side) shows aggregated time by JIRA issue key, with non-JIRA entries grouped by description. Stats are calculated for Monday-Sunday weeks based on the currently selected date.

## Timeline Visualization

The timeline (left panel) renders a vertical bar chart of the day's activities:
- Running entries: Green outline with colored fill
- Stopped entries: Solid color fill
- Overlapping entries: Shown with small colored dots
- Empty rows: Faint horizontal guide lines (solid at full hours, dashed at half hours, dotted otherwise)
- Time increments: Fixed resolution (1, 2, 5, 10, 15, 30, or 60 minutes per row)
- View window rounds to full hours; header shows actual entry times
- Timeline extends at least 10 minutes into the future from current time
- See [`src/dashboard/drawers/timeline/architecture.md`](src/dashboard/drawers/timeline/architecture.md) for full details

## Common Development Patterns

When adding new features:

1. **New input fields**: Add `cursor_pos: usize` to the `InputMode` variant, add the field to `cursor.rs:get_active_text_and_cursor()`, initialize `cursor_pos` at creation sites in `app.rs`.

2. **New keyboard shortcuts**: Add cases to the input handler for the appropriate `InputMode` (`dashboard/input.rs` for Normal/Editing/Creating, `settings/input.rs` for Settings).

3. **Database changes**: Add migration logic in `db.rs:init_schema()` to check for missing columns and alter table if needed.

4. **UI panels**: The main layout uses ratatui constraints. Modify `ui.rs:draw()` to adjust panel sizing.

5. **Configuration options**: Add fields to `Config` struct with serde defaults, update settings UI in `settings/ui.rs`.

6. **New integrations**: Add a variant to `IntegrationKind`, implement `log_work()`/`open_issue()` in a new submodule under `integrations/`, add dispatch arm in `integrations/mod.rs`. Update settings field layout in `settings/ui.rs` and `settings/input.rs`.

7. **Settings fields**: Field indices are dynamic based on active `IntegrationKind`. Update `settings_legacy_time_format_field()`, `cycle_color()`, `next_field()`/`previous_field()` in `app.rs`, and `is_color_field()` in `settings/input.rs`.

## Important: String Handling

**CRITICAL**: When working with string truncation or slicing in Rust, ALWAYS use character iteration, NOT byte indexing.

### ❌ WRONG (causes crashes with multi-byte characters like ä, ö, å):
```rust
let truncated = &my_string[..9];  // Panics if byte 9 is not a char boundary
```

### ✅ CORRECT:
```rust
let truncated: String = my_string.chars().take(9).collect();
```

**Why**: In UTF-8, characters like Nordic characters (ä, ö, å), emoji, and many non-ASCII characters use multiple bytes. Byte indexing `[..n]` will panic with "byte index is not a char boundary" if it lands in the middle of a multi-byte character.

**Always**:
- Use `.chars().count()` instead of `.len()` when checking character count
- Use `.chars().take(n).collect()` instead of `[..n]` for truncation
- Test with non-ASCII characters (ä, ö, å, emoji) to verify correctness

## Binary Output

The compiled binary is named `trackWork`. See Cargo.toml name field.