# AGENTS.md — Coding Agent Orientation

> This document helps AI coding agents understand the django-shreck project structure,
> conventions, and key design decisions so they can make effective contributions.

## Project Purpose

**django-shreck** is a reusable Django app that provides a rich TUI (Terminal User Interface)
as a replacement for `shell_plus`. It auto-imports all models, displays querysets as tables,
single objects as detail views, supports inline editing, side-by-side comparison,
admin integration, and a management command browser.

Full PRD: [`docs/PRD.md`](docs/PRD.md)

## Tech Stack

- **Python** ≥ 3.11 (developed on 3.13)
- **Django** ≥ 4.2
- **Textual** ≥ 1.0 — TUI framework (async, CSS-based layout)
- **ptpython** ≥ 3.0 — Enhanced Python REPL (future integration)
- **pytest** + **pytest-django** — Testing
- **Ruff** — Linting and formatting
- **Hatch** — Build system

## Directory Layout

```
django-shreck/
├── AGENTS.md                          ← You are here
├── README.md
├── LICENSE                            (MIT)
├── pyproject.toml                     (packaging, deps, build config)
├── ruff.toml                          (linter/formatter config)
├── Makefile                           (dev tasks: lint, format, test, run)
│
├── docs/
│   └── PRD.md                         (Product Requirements Document)
│
├── django_shreck/                     ← The Django app package
│   ├── __init__.py                    (AppConfig reference)
│   ├── apps.py                        (DjangoShreckConfig)
│   ├── app.py                         ★ Main Textual App (ShreckApp)
│   ├── messages.py                    (Custom Textual Message classes)
│   │
│   ├── management/
│   │   └── commands/
│   │       └── shreck.py              ★ Entry point: `manage.py shreck`
│   │
│   ├── services/                      ← Business logic (no TUI dependencies)
│   │   ├── __init__.py
│   │   ├── model_introspection.py     (auto-import models, field inspection)
│   │   ├── admin_url_resolver.py      (build admin URLs, open browser)
│   │   └── command_discovery.py       (list mgmt commands, parse args)
│   │
│   ├── widgets/                       ← Textual widgets (one per file)
│   │   ├── __init__.py
│   │   ├── shell_widget.py            ★ Interactive Python REPL
│   │   ├── result_panel.py            (container that switches views)
│   │   ├── detail_widget.py           (single object detail + edit)
│   │   ├── table_widget.py            (queryset as scrollable table)
│   │   ├── compare_widget.py          (side-by-side object diff)
│   │   └── command_browser_widget.py  (management command UI)
│   │
│   └── styles/
│       └── app.tcss                   (Textual CSS — green "Shreck" theme)
│
└── tests/
    ├── __init__.py
    ├── conftest.py                    (Django test settings, pytest config)
    ├── urls.py                        (minimal URL conf for admin)
    ├── test_services.py               (unit tests for service layer)
    └── test_messages.py               (unit tests for message classes)
```

## Key Entry Points

### Running the app
```
manage.py shreck
  → django_shreck/management/commands/shreck.py
    → django_shreck/app.py :: ShreckApp.run()
```

### Message flow (how shell results become views)
```
ShellWidget (eval/exec)
  → posts ShowDetail / ShowTable / ShowCompare message
    → ShreckApp.on_show_detail() / on_show_table() / on_show_compare()
      → ResultPanel.show_detail() / show_table() / show_compare()
        → mounts DetailWidget / TableWidget / CompareWidget
```

### Smart result routing (in ShellWidget._route_result)
- `QuerySet` with 1 result → `ShowDetail` (editable)
- `QuerySet` with 2 results → `ShowCompare`
- `QuerySet` with 3+ results → `ShowTable`
- Single `Model` instance → `ShowDetail`
- `list`/`tuple` of 2 `Model` instances → `ShowCompare`
- Anything else → printed as `repr()` in shell output

## Coding Conventions

1. **One widget per file** under `django_shreck/widgets/`
2. **Services are plain classes** with `@staticmethod` methods — no TUI dependencies
3. **Textual CSS** lives in `django_shreck/styles/` — widget-level CSS uses `DEFAULT_CSS`
4. **Custom messages** go in `django_shreck/messages.py`
5. **Type hints** everywhere — use `from __future__ import annotations`
6. **Line length** 120 characters (see `ruff.toml`)
7. **Formatting** — double quotes, space indentation (Ruff)
8. **Imports** — sorted with isort via Ruff, `django_shreck` is first-party

## Testing

- **Framework:** pytest + pytest-django
- **Settings:** Configured in `tests/conftest.py` (in-memory SQLite, minimal INSTALLED_APPS)
- **Run:** `make test` or `pytest tests/ -v`
- **Service tests** should not depend on TUI — test business logic directly
- **Widget tests** can use Textual's `App.run_test()` for headless testing

## Key Design Decisions

1. **eval/exec REPL** — We use Python's built-in `eval()`/`exec()` for the shell rather than
   embedding a full ptpython session. This keeps the TUI responsive (single event loop).
   ptpython integration is planned for a future phase.

2. **Textual messages** — Widget-to-app communication uses Textual's message system rather than
   direct method calls. This keeps widgets decoupled.

3. **Admin URL strategy** — Uses `webbrowser.open()` as best-effort, always shows URL in
   TUI notifications. Works on desktop, degrades gracefully on headless/SSH.

4. **ResultPanel as view switcher** — Instead of multiple screens, we use a single `ResultPanel`
   that mounts/unmounts child widgets. This keeps the shell always visible.

## Common Tasks for Agents

### Adding a new widget
1. Create `django_shreck/widgets/my_widget.py`
2. Add `DEFAULT_CSS` to the widget class
3. Export it from `django_shreck/widgets/__init__.py`
4. Add a message class to `django_shreck/messages.py` if needed
5. Wire up the message handler in `app.py`
6. Mount it from `ResultPanel`

### Adding a new service
1. Create `django_shreck/services/my_service.py`
2. Use `@staticmethod` methods
3. Export from `django_shreck/services/__init__.py`
4. Add tests in `tests/test_services.py`

### Modifying the layout
1. Edit `django_shreck/styles/app.tcss` for global styles
2. Edit widget's `DEFAULT_CSS` for widget-specific styles
3. Textual CSS docs: https://textual.textualize.io/css_types/

