Files
tasksquire/CLAUDE.md
Martin Pander 02fa2e503a Add things
2026-02-04 13:13:04 +01:00

7.3 KiB

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

# 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

# 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

# Lint with golangci-lint (via nix-shell)
golangci-lint run

Development Environment

The project uses Nix for development environment setup:

# 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:

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:

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:

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)