# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview TaskSquire is a Go-based Terminal User Interface (TUI) for Taskwarrior and Timewarrior. It uses the Bubble Tea framework (Model-View-Update pattern) from the Charm ecosystem. **Key Technologies:** - Go 1.22.2 - Bubble Tea (MVU pattern) - Lip Gloss (styling) - Huh (forms) - Bubbles (components) ## Build and Development Commands ### Running and Building ```bash # Run directly go run main.go # Build binary go build -o tasksquire main.go # Format code (always run before committing) go fmt ./... # Vet code go vet ./... # Tidy dependencies go mod tidy ``` ### Testing ```bash # Run all tests go test ./... # Run tests for specific package go test ./taskwarrior # Run single test go test ./taskwarrior -run TestTaskSquire_GetContext # Run with verbose output go test -v ./... # Run with coverage go test -cover ./... ``` ### Linting ```bash # Lint with golangci-lint (via nix-shell) golangci-lint run ``` ### Development Environment The project uses Nix for development environment setup: ```bash # Enter Nix development shell nix develop # Or use direnv (automatically loads .envrc) direnv allow ``` ## Architecture ### High-Level Structure TaskSquire follows the MVU (Model-View-Update) pattern with a component-based architecture: 1. **Entry Point (`main.go`)**: Initializes TaskSquire and TimeSquire, creates Common state container, and starts Bubble Tea program 2. **Common State (`common/`)**: Shared state, components interface, keymaps, styles, and utilities 3. **Pages (`pages/`)**: Top-level UI views (report, taskEditor, timePage, pickers) 4. **Components (`components/`)**: Reusable UI widgets (input, table, timetable, picker) 5. **Business Logic**: - `taskwarrior/`: Wraps Taskwarrior CLI, models, and config parsing - `timewarrior/`: Wraps Timewarrior CLI, models, and config parsing ### Component System All UI elements implement the `Component` interface: ```go type Component interface { tea.Model // Init(), Update(tea.Msg), View() SetSize(width int, height int) } ``` Components can be composed hierarchically. The `Common` struct manages a page stack for navigation. ### Page Navigation Pattern - **Main Page** (`pages/main.go`): Root page with tab switching between Tasks and Time views - **Page Stack**: Managed by `common.Common`, allows pushing/popping subpages - `common.PushPage(page)` - push a new page on top - `common.PopPage()` - return to previous page - `common.HasSubpages()` - check if subpages are active - **Tab Switching**: Only works at top level (when no subpages active) ### State Management The `common.Common` struct acts as a shared state container: - `TW`: TaskWarrior interface for task operations - `TimeW`: TimeWarrior interface for time tracking - `Keymap`: Centralized key bindings - `Styles`: Centralized styling (parsed from Taskwarrior config) - `Udas`: User Defined Attributes from Taskwarrior config - `pageStack`: Stack-based page navigation ### Taskwarrior Integration The `TaskWarrior` interface provides all task operations: - Task CRUD: `GetTasks()`, `ImportTask()`, `SetTaskDone()` - Task control: `StartTask()`, `StopTask()`, `DeleteTask()` - Context management: `GetContext()`, `GetContexts()`, `SetContext()` - Reports: `GetReport()`, `GetReports()` - Config parsing: Manual parsing of Taskwarrior config format All Taskwarrior operations use `exec.Command()` to call the `task` CLI binary. Results are parsed from JSON output. ### Timewarrior Integration The `TimeWarrior` interface provides time tracking operations: - Interval management: `GetIntervals()`, `ModifyInterval()`, `DeleteInterval()` - Tracking control: `StartTracking()`, `StopTracking()`, `ContinueTracking()` - Tag management: `GetTags()`, `GetTagCombinations()` - Utility: `FillInterval()`, `JoinInterval()`, `Undo()` Similar to TaskWarrior, uses `exec.Command()` to call the `timew` CLI binary. ### Custom JSON Marshaling The `Task` struct uses custom `MarshalJSON()` and `UnmarshalJSON()` to handle: - User Defined Attributes (UDAs) stored in `Udas map[string]any` - Dynamic field handling via `json.RawMessage` - Virtual tags (filtered from regular tags) ### Configuration and Environment - **Taskwarrior Config**: Located via `TASKRC` env var, or fallback to `~/.taskrc` or `~/.config/task/taskrc` - **Timewarrior Config**: Located via `TIMEWARRIORDB` env var, or fallback to `~/.timewarrior/timewarrior.cfg` - **Config Parsing**: Custom parser in `taskwarrior/config.go` handles Taskwarrior's config format - **Theme Colors**: Extracted from Taskwarrior config and used in Lip Gloss styles ### Concurrency - Both `TaskSquire` and `TimeSquire` use `sync.Mutex` to protect shared state - Lock pattern: `ts.mu.Lock()` followed by `defer ts.mu.Unlock()` - Operations are synchronous (no goroutines in typical flows) ### Logging - Uses `log/slog` for structured logging - Logs written to `app.log` in current directory - Errors logged but execution typically continues (graceful degradation) - Log pattern: `slog.Error("message", "key", value)` ## Code Style and Patterns ### Import Organization Standard library first, then third-party, then local: ```go import ( "context" "fmt" tea "github.com/charmbracelet/bubbletea" "tasksquire/common" "tasksquire/taskwarrior" ) ``` ### Naming Conventions - Exported types: `PascalCase` (e.g., `TaskSquire`, `ReportPage`) - Unexported fields: `camelCase` (e.g., `configLocation`, `activeReport`) - Interfaces: Often end in 'er' or describe capability (e.g., `TaskWarrior`, `TimeWarrior`, `Component`) ### Error Handling - Log errors with `slog.Error()` and continue execution - Don't panic unless fatal initialization error - Return errors from functions, don't log and return ### MVU Pattern in Bubble Tea Components follow the MVU pattern: - `Init() tea.Cmd`: Initialize and return commands for side effects - `Update(tea.Msg) (tea.Model, tea.Cmd)`: Handle messages, update state, return commands - `View() string`: Render UI as string Custom messages for inter-component communication: ```go type MyCustomMsg struct { data string } func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case MyCustomMsg: // Handle custom message } return m, nil } ``` ### Styling with Lip Gloss - Centralized styles in `common/styles.go` - Theme colors parsed from Taskwarrior config - Create reusable style functions, not inline styles ### Testing Patterns - Table-driven tests with struct slices - Helper functions like `TaskWarriorTestSetup()` - Use `t.TempDir()` for isolated test environments - Include `prep func()` in test cases for setup ## Important Implementation Details ### Virtual Tags Taskwarrior has virtual tags (ACTIVE, BLOCKED, etc.) that are filtered out from regular tags. See the `virtualTags` map in `taskwarrior/taskwarrior.go`. ### Non-Standard Reports Some Taskwarrior reports require special handling (burndown, calendar, etc.). See `nonStandardReports` map. ### Timestamp Format Taskwarrior uses ISO 8601 format: `20060102T150405Z` (defined as `dtformat` constant) ### Color Parsing Custom color parsing from Taskwarrior config format in `common/styles.go` ### VSCode Debugging Launch configuration available for remote debugging on port 43000 (see `.vscode/launch.json`)