# Agent / dev workflow

Use **Docker Compose `web` image** (see `docker-compose.yml` + `docker/Dockerfile`) for migrations, management commands, and tests — it includes `libmagic` and `development.txt` deps.

## Django template comments — NEVER use multi-line `{# … #}`

Django's lexer only matches `{# … #}` **within a single line** (its `tag_re` has no `re.DOTALL`),
so a `{# … #}` comment that spans multiple lines is NOT parsed as a comment — it renders as
**visible text on the rendered page**. Rules:

- One-liners → single-line `{# … #}`.
- Multi-line notes → `{% comment %} … {% endcomment %}`.
- Before `{% extends %}` → only single-line `{# … #}` (extends must be the first tag).

When touching any template, scan for and fix any multi-line `{# … #}` you find.

## One-off commands (Postgres + Redis must be up)

```bash
docker compose run --rm web sh -c "python manage.py migrate --noinput && python manage.py seed_roles"
docker compose run --rm web sh -c "pytest apps/core/permissions/tests/ apps/rims/grants/tests/ apps/rims/finance/tests/ -q --tb=short"
```

After model changes:

```bash
docker compose run --rm web python manage.py makemigrations
```

## Full stack (UI in browser)

```bash
docker compose up -d web
```

- App URL: `http://127.0.0.1:${WEB_HOST_PORT:-8000}/` (this repo’s `.env` may set `WEB_HOST_PORT=8090`). For **Cursor Browser** (MCP) or similar automation, prefer **`http://localhost:<port>`** if `127.0.0.1` does not load.
- Demo data (minimal): `docker compose exec web python manage.py seed_rims` (default demo password: **`password123`**; override with `--password <pw>`).
- **PRD / browser flows:** same command with `--comprehensive` (after `seed_roles`). Add **`--reset`** to delete seeded grant calls (and cascaded applications, awards, finance, projects) before inserting — fixes stale rows after model/workflow changes. **`--reset`** also drops legacy slug `reviewer-queue-e2e-call` (old duplicate reviewer-queue seed). Extra accounts (same password): `finance@demo.local`, `mel@demo.local`, `reviewer2@demo.local`, `award.recipient@demo.local`, `eligibility.applicant@demo.local`, `interview.applicant@demo.local`, `conflict.applicant@demo.local`, `blind.applicant@demo.local`.
- **Rich demo content (Quill / UI):** Comprehensive seed fills **first/last names**, **phone**, **`email_verified`**, profile **bios**, **HTML** `description` / `guidelines` on all demo calls, **`Application.proposal`**, **award narrative**, **disbursement justification**, **project summary**, plus a seeded **Partner** (notes) and **Scholarship** (description) for operations/scholarships smoke. Use **`--reset --comprehensive`** after changing seed text so DB matches.
- **Nuclear dev DB (all apps emptied, schema kept):** `docker compose exec web python manage.py flush --noinput` then `migrate --noinput`, `seed_roles`, optionally `seed_core` (`DJANGO_SUPERUSER_EMAIL`; if `DJANGO_SUPERUSER_PASSWORD` is empty and **DEBUG=True**, defaults to **`password123`** and re-applies on each run), then `seed_rims --reset --comprehensive` (demo `@demo.local` users default to **`password123`** unless you pass `--password`).

| Call slug | Use in browser |
|-----------|----------------|
| `demo-seed-call` | Default manager list; seeded **award** + **budget** + **project/milestones** (dashboard rows) |
| `e2e-reviewer-call` | Scholar application; list title **E2E reviewer queue (scholar pending)**; **reviewer@demo.local** pending review (re-seed after submitting reviews) |
| `e2e-eligibility-call` | **Ineligible** application (PhD-only rule; eligibility applicant has blank degree) |
| `e2e-interview-call` | **Shortlisted** + **interview required**; award blocked until interview marked complete |
| `e2e-conflict-call` | Two **submitted** reviews with **different recommendations** (manager conflict panel) |
| `e2e-blind-call` | **Blind review** + anonymized code; reviewer queue shows code not email |

## Celery (optional)

```bash
docker compose up -d celery_worker celery_beat
```

**Email / notifications (PRD 6.1):** Publish and reminder tasks queue via Celery; **delivery requires SMTP (or similar) in settings**. Manual or Browser-driven checks do **not** assert inbox delivery—check worker logs or integration env.

## RIMS browser testing (PRD 6.1)

**Preconditions:** `docker compose up -d web`, `seed_roles`, `seed_rims --reset --comprehensive`. Sign in as any `@demo.local` account with **`password123`** (or the password you passed to `--password`). Prefer `http://localhost:<WEB_HOST_PORT>` with **Browser**. Email/notifications are out of scope unless SMTP is configured.

### Event-based flows (test state changes, not just page loads)

**Flow 1 — Review → shortlist → award (`e2e-reviewer-call`)**  
After a fresh comprehensive seed, the scholar’s application is **under review** with one **pending** review for `reviewer@demo.local`.

1. **Reviewer:** Sign in → `/rims/grants/reviews/` → open **Score** for the row titled **E2E reviewer queue (scholar pending)** (or go directly to `/rims/grants/reviews/<pk>/` — resolve `pk` with the shell snippet below).
2. On **Submit review**, fill every **scoring** spinbutton (must respect each criterion’s max), add **Overall recommendation** text → **Submit review** → expect flash *Review submitted* and the queue no longer lists that call.
3. **Grants manager:** Sign in → `/rims/grants/manage/calls/e2e-reviewer-call/applications/` → open the application → **Shortlist** (POST) → expect *Application shortlisted* and **Create award**.
4. **Create award:** `/rims/grants/manage/applications/<app_pk>/award/` → **Award amount**, **Project end date** (required) → **Create award** → redirect to `/rims/grants/manage/awards/` with *Award created*.

Resolve pending review id after seed (repeat whenever you re-seed):

```bash
docker compose exec web python manage.py shell -c "
from apps.rims.grants.models import Review, GrantCall
c = GrantCall.objects.get(slug='e2e-reviewer-call')
r = Review.objects.filter(application__call=c, submitted_at__isnull=True).first()
print(r.pk if r else 'none')
"
```

Application id for that call (for shortlist/award URLs):

```bash
docker compose exec web python manage.py shell -c "
from apps.rims.grants.models import Application, GrantCall
a = Application.objects.get(call__slug='e2e-reviewer-call')
print(a.pk, a.status)
"
```

**Flow 2 — Interview gate → award (`e2e-interview-call`)**  
Seed leaves one application **shortlisted** with **interview required** and interview **not** complete.

1. **Grants manager:** `/rims/grants/manage/calls/e2e-interview-call/applications/` → open the app → **Mark interview complete** → **Create award** (same award form as flow 1).

**Flow 3 — Finance (post-award)**  
Use seeded `demo-seed-call` award: **finance@demo.local** → `/rims/finance/disbursements/` → open a **submitted** request → **Approve** or **Reject** (confirm status changes).

**Other smoke (permissions / data)**  
Eligibility (`e2e-eligibility-call`), conflict panel (`e2e-conflict-call`), blind queue (`e2e-blind-call`), **Projects** milestones, **Operations** partners, **Scholarships** programmes — use the call-slug table above; still **perform at least one POST** (e.g. shortlist, ack conflict, interview toggle) where the PRD scenario applies, not only GET navigation.
