added git worktrees to the plan

This commit is contained in:
2026-05-04 17:26:24 +02:00
parent 650f3456cb
commit d79c7ed08c

View File

@@ -19,24 +19,93 @@ The intended outcome is one continuous batch of work that ships: secure auth (lo
---
## Phase 0 — Git Workflow
### 0.1 Create a worktree
From your main clone (assume it lives at `~/tutortool`):
```sh
git worktree add ../tutortool-unified main
cd ../tutortool-unified
git switch -c feature/unified-fixes-room-editor
```
All implementation work happens in `../tutortool-unified`.
The original clone stays clean on `main`.
### 0.2 Commit discipline
- One logical commit per Phase/sub-phase, e.g.:
- `feat(security): fix RUSTSEC-2023-0071 via aws_lc_rs`
- `fix(api): handle empty 200 body + fix check-in typing`
- `feat(tutors): add is_active flag, deactivate + safe delete`
- `feat(rooms): pixel→grid migration + layout validators`
- `feat(canvas): fix drag/resize, click-to-select in edit mode`
- `feat(viz): replace SeatMap with dynamic RoomCanvas`
- `make lint && make test` must pass **before** each commit.
Gate with a pre-commit hook if you like:
```sh
# .git/hooks/pre-commit
#!/bin/sh
cd backend && cargo clippy --all-targets -- -D warnings && cargo test
```
### 0.3 Open the PR
Once all phases are done and CI is green:
```sh
git push -u origin feature/unified-fixes-room-editor
# then open a PR against main via Gitea/GitHub UI
# title: "Unified fixes, tutor lifecycle, room editor refactor"
# body: link this plan doc, list phases, reference Playwright spec results
```
Merge strategy: **squash-merge** each phase-branch if you split work, or a single **merge commit** if working on one branch (keeps phase history readable).
### 0.4 Post-merge cleanup
```sh
cd ~/tutortool # back to main clone
git pull # fast-forward to merged state
git worktree remove ../tutortool-unified
git branch -d feature/unified-fixes-room-editor
```
### 0.5 Verification gate before merge
PR must have:
- [ ] `make test` passes (all backend + new Phase 2.4/3.2/3.7 tests)
- [ ] `make lint` passes (zero Clippy warnings, zero TS errors)
- [ ] `sqlx migrate run` clean on fresh DB (migrations 003 + 004)
- [ ] All new Playwright specs green (`rooms`, `checkin-dynamic`, `admin-live-dynamic`, `admin-tutors`, `admin-rooms-delete`)
- [ ] `cargo audit` clean (no RUSTSEC-2023-0071)
## Phase 1 — Security & Quick Fixes
### 1.1 RUSTSEC-2023-0071 (Marvin Attack)
- `backend/Cargo.toml` — set `jsonwebtoken` to `features = ["aws_lc_rs"]`.
- `backend/audit.toml` — remove `ignore = ["RUSTSEC-2023-0071"]`.
- `.gitea/workflows/ci.yml` and `.gitea/workflows/release.yml` — drop `--ignore RUSTSEC-2023-0071`.
### 1.2 JSON parse error on empty 200 body
- `frontend/src/lib/api.ts:46` — currently only 204 is short-circuited. Extend the empty-body branch to also handle 200-with-empty-body (probe `content-length === '0'` or fall through `await res.text()` and return `{} as T` if empty). Triggers from `assignTutor`, `unassignTutor`, etc.
### 1.3 Check-in API typing
- `frontend/src/lib/api.ts` — `checkin.post` currently typed `Promise<Attendance>`; backend returns `{ok: true}`. Change to `Promise<{ok: boolean}>`.
- `frontend/src/routes/s/[code]/+page.svelte:82` — drop the local-state assignment from the response; rely on the subsequent `loadInfo()` to populate `myAttendance`.
### 1.4 Attendance count not assigned (issues doc #2)
- `frontend/src/routes/s/[code]/+page.svelte` `loadInfo` — assign `attendances = checkinAttendances` so the "Anwesende N / M" counter updates.
### 1.5 Admin logout UI
- `frontend/src/lib/components/TutorShell.svelte` (where the actual sidebar nav lives — `frontend/src/routes/admin/+layout.svelte` only renders the shell) — add an "Abmelden" entry at the bottom of the nav. On click: `await api.auth.logout()` → `auth.logout()` → `goto('/admin/login')`. Re-export `auth` and `api` if needed.
---
@@ -46,6 +115,7 @@ The intended outcome is one continuous batch of work that ships: secure auth (lo
The user wants **both** soft-deactivate (preserve history, hide from pickers) **and** real hard-delete (with safety pre-check).
### 2.1 Schema migration
- New file `backend/migrations/004_tutor_is_active.sql`:
```sql
ALTER TABLE tutors ADD COLUMN is_active BOOLEAN NOT NULL DEFAULT 1;
@@ -53,6 +123,7 @@ The user wants **both** soft-deactivate (preserve history, hide from pickers) **
No backfill needed; existing rows default to active.
### 2.2 Backend changes
**File:** `backend/src/routes/tutors.rs`
- **`Tutor` model** (`backend/src/models.rs:11-17`) — add `is_active: bool`.
@@ -62,13 +133,15 @@ The user wants **both** soft-deactivate (preserve history, hide from pickers) **
- `tutor_courses WHERE tutor_id = ?`
- `slots WHERE tutor_id = ?`
- `notes WHERE tutor_id = ?`
If any non-zero, return `AppError::Conflict(format!("Tutor:in hat noch {c} Kurszuordnung(en), {s} Slot(s) und {n} Notiz(en). Bitte zuerst entfernen oder deaktivieren."))`.
If any non-zero, return `AppError::Conflict(format!("Tutor:in hat noch {c} Kurszuordnung(en), {s} Slot(s) und {n} Notiz(en). Bitte zuerst entfernen oder deaktivieren."))`.
`AppError::Conflict` already maps to 409 with `{"error": msg}` (`backend/src/error.rs`).
- **Auth login** (`backend/src/routes/auth_routes.rs:22-96`) — reject inactive tutors with `AppError::Unauthorized` (same response shape as wrong-password to avoid info leakage). Add this check after password verification.
- **Tutor pickers** — grep for `SELECT … FROM tutors` outside of `list_tutors` and `auth_routes.rs`. Likely sites: `backend/src/routes/courses.rs` (tutor assignment list), `backend/src/routes/sessions.rs` or `slots` creation flow. Each must add `WHERE is_active = 1`.
### 2.3 Frontend changes
**File:** `frontend/src/routes/admin/tutors/+page.svelte`
- Show three status pills: `Superadmin` / `Tutor:in` / `Inaktiv` (combine `is_superadmin` + `is_active`).
@@ -77,6 +150,7 @@ The user wants **both** soft-deactivate (preserve history, hide from pickers) **
- **API client** `frontend/src/lib/api.ts` — add `api.admin.tutors.setActive(id, is_active)` calling `PATCH /admin/tutors/{id}/active`.
### 2.4 Tests (`backend/src/routes/tutors.rs` `#[cfg(test)]` block)
Pattern after `backend/src/routes/rooms.rs:184-322` and use `backend/src/test_helpers.rs`.
- `delete_tutor_with_no_refs_succeeds` → 204, row gone.
@@ -126,19 +200,21 @@ Reference: `conductor/room-editor-refactor-visualization.md`. One-liner per task
## Verification
### Automated
- `make test` — backend suite passes, including:
- Phase 2.4 tutor delete + activate tests
- Phase 3.2 layout validator tests
- Phase 3.7 room delete tests
- `sqlx migrate run` against a clean DB — migrations 003 and 004 apply cleanly.
- `make lint` — zero warnings (per the project's mandate).
- `frontend/tests/rooms.spec.ts` *(new)* — create room, drag/snap, save/reload, assert coords preserved.
- `frontend/tests/checkin-dynamic.spec.ts` *(new)* — custom layout, click seat, `POST /api/checkin` succeeds, seat turns green.
- `frontend/tests/admin-live-dynamic.spec.ts` *(new)* — student appears on correct seat in tutor live view.
- `frontend/tests/admin-tutors.spec.ts` *(new)* — (i) delete tutor with no refs → row disappears; (ii) delete tutor attached to a course → red error mentions `Kurszuordnung`; unassign → retry succeeds; (iii) deactivate → tutor cannot log in; (iv) reactivate → tutor can log in again.
- `frontend/tests/admin-rooms-delete.spec.ts` *(new)* — delete unused room succeeds; delete room attached to a slot → 409 message visible.
- `frontend/tests/rooms.spec.ts` _(new)_ — create room, drag/snap, save/reload, assert coords preserved.
- `frontend/tests/checkin-dynamic.spec.ts` _(new)_ — custom layout, click seat, `POST /api/checkin` succeeds, seat turns green.
- `frontend/tests/admin-live-dynamic.spec.ts` _(new)_ — student appears on correct seat in tutor live view.
- `frontend/tests/admin-tutors.spec.ts` _(new)_ — (i) delete tutor with no refs → row disappears; (ii) delete tutor attached to a course → red error mentions `Kurszuordnung`; unassign → retry succeeds; (iii) deactivate → tutor cannot log in; (iv) reactivate → tutor can log in again.
- `frontend/tests/admin-rooms-delete.spec.ts` _(new)_ — delete unused room succeeds; delete room attached to a slot → 409 message visible.
### Manual
1. `make seed-demo`. Open `Admin → Rooms → Room A` — all elements at sensible grid positions (no values > 50).
2. Drag and resize an element; release outside the SVG; confirm no stranded drag.
3. Click an element in **edit mode** — sidebar populates (issue #4 regression).
@@ -158,8 +234,8 @@ Reference: `conductor/room-editor-refactor-visualization.md`. One-liner per task
- `backend/Cargo.toml`
- `backend/audit.toml`
- `.gitea/workflows/ci.yml`, `.gitea/workflows/release.yml`
- `backend/migrations/003_normalize_room_layout_units.sql` *(new)*
- `backend/migrations/004_tutor_is_active.sql` *(new)*
- `backend/migrations/003_normalize_room_layout_units.sql` _(new)_
- `backend/migrations/004_tutor_is_active.sql` _(new)_
- `backend/demo/demo_seed.sql`
- `backend/src/models.rs`
- `backend/src/routes/tutors.rs`
@@ -171,7 +247,7 @@ Reference: `conductor/room-editor-refactor-visualization.md`. One-liner per task
- `frontend/src/lib/api.ts`
- `frontend/src/lib/types.ts`
- `frontend/src/lib/RoomCanvas.svelte`
- `frontend/src/lib/components/SeatMap.svelte` *(delete in Phase 4.5)*
- `frontend/src/lib/components/SeatMap.svelte` _(delete in Phase 4.5)_
- `frontend/src/lib/components/TutorShell.svelte` (logout button)
- `frontend/src/routes/admin/tutors/+page.svelte`
- `frontend/src/routes/admin/rooms/+page.svelte`