# CLAUDE.md

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

## Project Overview

DoList is a command-line todo list manager written in Python that stores tasks in a SQLite database. It provides both a CLI interface and an interactive shell mode for managing tasks with features like tags, status tracking, notes, and reminders.

## Installation & Setup

This project has been modernized to use Python 3.10+ with `uv` for package management.

```bash
# Install using uv (recommended)
uv sync

# Run the application
uv run dolist [command]

# Or install in development mode
uv pip install -e .
```

**Legacy Installation (Python 2.7)**:
```bash
# Install dependencies
pip install -r requirements.txt

# Install the package
python setup.py install
```

The application can be run as:
- `uv run dolist` - Using uv (Python 3.10+)
- `python dolist/do.py` - Direct execution (legacy)
- `dolist` - After installation

## Core Architecture

### Database Layer (database.py)
- Uses lightweight SQLite database wrapper (replaced web2py DAL)
- Supports SQLite databases with query building
- Located at `~/.config/dolist/tasks.db` by default (or `$XDG_CONFIG_HOME/dolist/tasks.db`)
- Configuration stored in `~/.config/dolist/config.toml` file (TOML format)
- Backwards compatible with legacy `~/.dopy/dopy.db` location
- Database can be switched using `--use=<db>` argument

### Data Model (taskmodel.py)
The `Task` class is a wrapper around database rows with properties:
- `name` - Task description
- `tag` - Category/tag (default: "default")
- `status` - One of: new, working, done, cancel, post
- `reminder` - Optional reminder string
- `notes` - List of note strings
- `created_on` - Creation timestamp
- `deleted` - Soft delete flag

All property setters automatically commit changes to the database.

### Main Application (do.py)
Entry point using docopt for argument parsing. Key functions:
- `database(DBURI)` - Initialize DAL and define schema
- `shell()` - Interactive Python shell with `tasklist` variable
- `add()` - Create new task
- `ls()` - List tasks with filtering
- `done()` - Mark task as complete
- `rm()` - Soft delete task
- `get()` - Enter interactive shell for single task
- `note()` / `show()` - Manage task notes

### Display Layer
- `printtable.py` - ASCII table rendering with color support (strips ANSI codes for width calculation)
- `colors.py` - Colored output using termcolor library with status-specific color schemes

## Testing

The project includes a comprehensive test suite with 87 tests using pytest.

```bash
# Run all tests
uv run pytest

# Run with verbose output
uv run pytest -v

# Run specific test file
uv run pytest tests/test_colors.py

# See README_TESTS.md for detailed testing guide
```

Test coverage:
- **test_colors.py**: 22 tests for color formatting
- **test_printtable.py**: 33 tests for table rendering
- **test_taskmodel.py**: 18 tests for Task model
- **test_do.py**: 14 tests for application functions

All tests pass with Python 3.10+. See [README_TESTS.md](README_TESTS.md) for complete testing documentation.

## Common Commands

### Running the Application
```bash
# Interactive shell mode
python dolist/do.py

# Add a task
python dolist/do.py add "Task name" [tag] [status] [--reminder=text] [--use=dbname]

# List tasks
python dolist/do.py ls [--all] [--tag=tag] [--status=status] [--search=term]

# Mark task as done
python dolist/do.py done <id>

# Remove task
python dolist/do.py rm <id>

# Add note to task
python dolist/do.py note <id> "Note text"

# Show task with notes
python dolist/do.py show <id>

# Interactive task editing
python dolist/do.py get <id>
```

### Database Management
```bash
# Use alternate database
python dolist/do.py add "Task" --use=work

# List from alternate database
python dolist/do.py ls --use=work

# Reset database (with confirmation and timestamped backup)
python dolist/do.py reset
```

## Key Implementation Details

### Task Status Values
Valid status values: `new`, `in-progress`, `done`, `cancel`, `post`

Default filtering in `ls` excludes done/cancel/post unless `--all` or `--status` is specified.

### Status Commands
- `dolist start <id>` or `dolist in-progress <id>` - Mark task as in-progress
- `dolist new <id>` - Mark task as new (reset to initial state)
- `dolist done <id>` - Mark task as completed/done
- `dolist post <id>` - Mark task as postponed/delayed
- `dolist cancel <id>` - Mark task as cancelled

### Configuration
- Config file: `~/.config/dolist/config.toml` (TOML format)
- Legacy config: `~/.dopyrc` (automatically migrated)
- Data directory: `~/.config/dolist/` (or `$XDG_CONFIG_HOME/dolist`)
- Legacy data directory: `~/.dopy/` (backwards compatible)
- Default database: `sqlite://tasks.db`

### Interactive Shell Mode
When running `dolist get <id>`, an interactive Python shell is spawned with:
- `task` object - The selected task with editable properties
- `task.delete()` - Delete the task
- Direct property access/modification (auto-commits)

When running bare `dolist`, shell includes:
- `tasklist` - List of all non-deleted Task objects
- `db` - Database connection
- `tasks` - Table definition

### Soft Deletes
Tasks are never truly deleted; the `deleted` field is set to `True` and they're filtered from queries.

## Development Notes

### Python 3.10+ Conversion Status

The codebase has been partially converted to Python 3.10+:

**Completed**:
- Modern `pyproject.toml` with `uv` support
- Python 3 print functions
- Updated imports (relative imports within package)
- `types.ListType` → `isinstance(x, list)`
- Pickle file operations (binary mode)
- Python 3 compatibility layer in dal.py (basestring, xrange, unicode)
- Package structure with `__init__.py`
- Console script entry point

**Known Issues**:
- The vendored web2py DAL (dal.py, ~380KB) has deep Python 2/3 compatibility issues
- Row object attribute access is broken in Python 3 (needs DAL update or migration to modern ORM)
- Many regex patterns generate SyntaxWarnings (use raw strings)
- `cgi.FieldStorage` deprecated in Python 3.13+ (conditional import added)

**Recommendations**:
1. Consider migrating from web2py DAL to a modern ORM (SQLAlchemy, Peewee)
2. Or update to latest web2py DAL/pyDAL which has better Python 3 support
3. Add test suite before further refactoring
4. Fix regex SyntaxWarnings by using raw strings

### Legacy Information

- No test suite present
- Large vendored dependency (dal.py from web2py)
- Terminal color support via termcolor library
- Uses docopt for CLI argument parsing

## TUI Architecture (dolist/tui.py)

The Textual TUI provides a modern terminal interface with vim-like navigation and features.

### State Management

The `DoListTUI` app maintains several state variables to preserve user context across refreshes:

```python
# Filter state
active_status_filters: set[str]  # e.g., {'new', 'done'}
search_filter: dict               # e.g., {'tag': ['work'], 'text': 'test'}

# Sort state
sort_column: str | None           # e.g., 'name', 'created', 'status'
sort_direction: str               # 'asc' or 'desc'

# UI state
autorefresh_enabled: bool
autorefresh_timer: Timer | None

# Scroll preservation
last_scroll_y: int
last_selected_task_id: int | None
```

### Key Components

#### Modal Screens
- **SearchOverlay** - Vim-like search overlay (triggered by `/`)
  - Live filtering as user types
  - Supports complex search syntax (text, tag=, status=)
  - ESC to cancel, Enter to apply

- **AddTaskScreen** - Modal for creating new tasks
- **EditTaskScreen** - Modal for editing existing tasks
- **ConfirmDeleteScreen** - Confirmation dialog for deletions

#### Command Provider
- **DoListCommandProvider** - Custom command provider for command palette
  - Sorting commands (by name, created, status, tag, id)
  - Refresh and quit commands
  - Triggered by `:` key

### Query Building Pattern

The TUI builds database queries dynamically based on active filters:

```python
def build_query():
    query = tasks_table.deleted != True

    # Apply search filters (override status filters if specified)
    if 'status' in search_filter:
        query &= tasks_table.status.belongs(search_filter['status'])
    elif active_status_filters:
        query &= tasks_table.status.belongs(list(active_status_filters))
    else:
        # Default: show active tasks only
        query &= ~tasks_table.status.belongs(["done", "cancel", "post"])

    # Apply text search
    if 'text' in search_filter:
        query &= tasks_table.name.like(f"%{search_filter['text']}%")

    # Apply tag filters (OR logic)
    if 'tag' in search_filter:
        tag_conditions = [tasks_table.tag == t for t in search_filter['tag']]
        query &= reduce(lambda a, b: a | b, tag_conditions)

    return query
```

### Sorting Implementation

Sorting is applied after query execution using Python's `sorted()`:

```python
def apply_sort(rows):
    if not sort_column:
        return rows

    reverse = sort_direction == 'desc'

    if sort_column == 'name':
        return sorted(rows, key=lambda r: r.name.lower(), reverse=reverse)
    elif sort_column == 'created':
        return sorted(rows, key=lambda r: r.created_on, reverse=reverse)
    # ... etc
```

### State Preservation

The `refresh_tasks()` method ensures all state is preserved:

1. Store current selection (task ID and row index)
2. Build query with active filters
3. Fetch and sort rows
4. Clear and rebuild table
5. Restore selection if task still visible
6. Update column headers with sort indicators

### Search Syntax Parser

The `parse_search()` function implements the vim-like search syntax:

```python
# Examples:
"/test"              → {'text': 'test'}
"/tag=work"          → {'tag': ['work']}
"/tag=work,personal" → {'tag': ['work', 'personal']}
"/status=new,done"   → {'status': ['new', 'done']}
"/tag=work test"     → {'tag': ['work'], 'text': 'test'}
```

### Auto-Refresh

Auto-refresh uses Textual's `set_interval()`:

```python
if autorefresh_enabled:
    autorefresh_timer = self.set_interval(
        autorefresh_interval,
        self.refresh_tasks
    )
```

The timer is properly cleaned up on exit to prevent memory leaks.

### Configuration

TUI settings are stored in `~/.config/dolist/config.toml`:

```toml
[ui]
theme = "textual-dark"  # or "textual-light"

[tui]
autorefresh_interval = 30  # seconds, 0 to disable
```

### Event Handlers

Key event handlers:
- `on_mount()` - Initialize table and focus
- `on_button_pressed()` - Handle button clicks (status filters, actions)
- `on_data_table_header_selected()` - Handle column sorting
- `on_data_table_row_selected()` - Handle row selection (edit task)

### Key Bindings

| Binding | Action | Description |
|---------|--------|-------------|
| `Ctrl+Q` | quit | Exit the TUI |
| `a` | add_task | Open add task dialog |
| `r` | refresh | Manual refresh |
| `/` | open_search | Open search overlay |
| `:` | command_palette | Open command palette |
| `Enter` | edit_task | Edit selected task |
| `Esc` | dismiss | Close modal screens |
