docs: phase 3b.1 manual-sessions session log + roadmap note

This commit is contained in:
Bas van Rossem
2026-06-17 22:54:18 +02:00
parent 7d3daaa760
commit 84677080fc
2 changed files with 108 additions and 4 deletions

View File

@@ -0,0 +1,98 @@
# Session: 2026-06-17 — Phase 3b·1 (Manual session entry/edit + admin stop/fix + worker convergence)
## Goal
Let an admin take full control of sessions: list/filter every session, manually create a
completed session for a worker, edit any session (server recomputes duration), and
stop/cancel a worker's active session — and make the worker's stopwatch converge to those
admin changes within ~15 s with no stuck state. No DB migration (reuses the existing
`work_sessions` columns, incl. the pause fields from the prior cycle).
Spec: `docs/superpowers/specs/2026-06-17-phase-3b1-manual-sessions-design.md`;
plan: `docs/superpowers/plans/2026-06-17-phase-3b1-manual-sessions.md`.
## Work done
Implemented task-by-task per the plan (TDD throughout), one commit per task:
- **Task 1 — Shared contracts** (`00993c6`). `@solelog/shared` gains
`CreateManualSessionInput` (worker id + activity + insole type + pair count +
`start_time`/`end_time` + `paused_seconds` default 0 + optional `notes`) and
`AdminUpdateSessionInput` (editable fields incl. nullable `end_time`/`insole_type` and a
`status`), with their inferred types. Duration is never accepted from the client.
- **Task 2 — Backend admin session write endpoints + users roster** (`53c66ee`). In
`apps/api/src/routes/admin.ts` (behind the existing `/api/admin/*` admin guard):
`GET /api/admin/users` (id/name/email, ordered by name), `POST /api/admin/sessions`
(validates the worker + activity, rejects `end < start` and `paused > span`, stores
`source='manual'`, `status='completed'`, server-computed `duration_seconds`),
`PUT /api/admin/sessions/:id` (recomputes duration; nullable `end_time` keeps an active
row open), and `POST …/:id/stop` (folds any open pause span, `end=now`, completes) /
`…/:id/discard` (`status='discarded'`, `end=now`). All session responses go through
`toWorkSession`. Workers hitting any of these get 403; stop/discard on an
already-closed session → 409.
- **Task 3 — Admin api hooks + Sessions screen** (`b536641`). `api/admin-sessions.ts`:
`useAllSessions`, `useAdminUsers`, `useCreateManualSession`, `useUpdateSession`,
`useAdminStopSession`, `useAdminDiscardSession` (mutations invalidate
`['admin','sessions']`). New `screens/Sessions.tsx` (title "Sessies", status `<select>`
filter alle/actief/voltooid/geannuleerd, `+ Nieuwe registratie`, a row per session with
worked time + a `Pauze …` line when paused, ✎ on all rows, Stop/Annuleer on active rows).
Sidebar gains a "Sessies" nav item and drops "Handmatig" from the soon group; `App.tsx`
gets the `/sessies` route; table/form/action styles added to `styles.css`.
- **Task 4 — Admin create/edit session form** (`69b46be`). `components/SessionForm.tsx`
(`mode: 'create' | 'edit'`): create mode shows a worker picker (`useAdminUsers`) and posts
`CreateManualSessionInput`; edit mode prefills from the session, hides the worker picker,
and PUTs the changed fields. Activity `<select>`, insole-type toggles, pair-count stepper,
start/end `datetime-local` (built into ISO), paused input, status `<select>` (edit only),
notes, and a derived "gewerkt" line (`end start paused`). Wired into Sessions:
`+ Nieuwe registratie` opens create, ✎ opens edit.
- **Task 5 — Stop/Annuleer on the Live view** (`f1ec249`). The active `LiveCard` gains Stop
(`POST …/:id/stop`) and Annuleer (`…/discard`) buttons via the admin hooks; on success the
active query invalidates and the card drops off.
- **Task 6 — Worker poll + reconcile to server truth** (`7d3daaa`).
`useActiveSessions` gets `refetchInterval: 15000`. The stopwatch's active-session effect
now resets to idle and shows **"Deze sessie is door de beheerder gestopt."** when the
worker's local session id is absent from the server's active list (covers admin stop,
discard, and edit-to-completed alike); a 409 from stop/discard resets locally with no
error surfaced. The existing "adopt an active session when idle" recovery is kept.
- **Task 7 — Docs, lint, verification** (this task). Lint/format clean, full green matrix,
in-process endpoint coverage, and this session log + roadmap note.
## Verification (Task 7)
- `npx oxlint` — clean (exit 0).
- `npx oxfmt --check` over the 16 cycle-changed source files — "All matched files use the
correct format" (earlier tasks formatted as they went; nothing to reformat).
- `yarn workspace @solelog/api typecheck` — pass; `test`**75 passed** (12 files).
- `yarn workspace @solelog/admin typecheck` — pass; `test`**36 passed** (7 files);
`build` — pass (vite, 91 modules).
- `yarn workspace @solelog/worker typecheck` — pass; `test`**30 passed** (8 files);
`build` — pass (vite, 91 modules).
- **Smoke** — the plan's live-server smoke was deliberately driven **in-process** instead of
by starting the API (`createApp()` + `app.request` in `apps/api/test/admin.test.ts`), so
port 3000 was never bound — this avoids the Windows libsql lock trap. The in-process tests
cover every smoke scenario end-to-end: `GET /api/admin/users` (admin 200 / worker 403);
`POST /api/admin/sessions` with `paused_seconds: 600` over a 1 h span →
`source='manual'`, `status='completed'`, `duration_seconds === 3000` (3600 600);
`end < start` → 400; unknown user/activity → 404; `PUT` edit recomputes duration;
`…/:id/stop` on another worker's active session → completed with computed duration;
`…/:id/discard``status='discarded'`; stop after discard → 409; worker token on any
write → 403.
## Outcome
Phase 3b·1 is implemented and green across all three workspaces. An admin can list and
filter every session, manually log a completed session for a worker (duration always
derived server-side and excluding paused time, `source='manual'`), edit any session,
and stop or cancel a worker's running session from either the **Sessies** screen or the
**Live** cards. The worker stopwatch polls every 15 s and reconciles to server truth — when
an admin stops/cancels/edits a worker's active session, the worker's clock resets and shows
"Deze sessie is door de beheerder gestopt." within ~15 s, with no stuck state (a racing 409
also resets cleanly). Roadmap Phase 3 status updated: **3b·1 done; reports/export and user
management remain**.
The two unrelated working-tree edits to `.env.prod.example` / `docker-compose.prod.yml`
(maintainer's deploy work) were left untouched — out of this cycle's scope.
## Next (Phase 3b remainder)
- Reports + all-users filtered CSV export (current `/api/export` is self-scoped).
- User management via better-auth `/api/auth/admin/*`.