Files
solelog/docs/sessions/2026-06-17-phase-3b1-manual-sessions.md

99 lines
6.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/*`.