docs: phase 3b.1 manual-sessions session log + roadmap note
This commit is contained in:
@@ -180,10 +180,16 @@ Each phase keeps the system working and is its own spec → plan → build cycle
|
||||
**3a implemented** (`apps/admin`; plan `docs/superpowers/plans/2026-06-17-phase-3a-admin-panel.md`):
|
||||
admin-only login (rejects non-admins via `role` on `/api/me`), the sidebar shell, the live
|
||||
active-work view (`/api/admin/sessions/active`, 5 s auto-refresh, read-only), and activity
|
||||
management (handelingen CRUD on `/api/activities`). **3b remaining:** reports/export
|
||||
(all-users filtered CSV — current `/api/export` is self-scoped), user management
|
||||
(better-auth `/api/auth/admin/*`), and manual entry/edit + admin stop/fix of a running
|
||||
session (needs new backend endpoints).
|
||||
management (handelingen CRUD on `/api/activities`).
|
||||
**3b·1 implemented** (plan `docs/superpowers/plans/2026-06-17-phase-3b1-manual-sessions.md`):
|
||||
manual session entry/edit + admin stop/discard of a running session (new admin write
|
||||
endpoints `POST/PUT /api/admin/sessions[/:id]`, `…/:id/stop`, `…/:id/discard`, plus a
|
||||
`GET /api/admin/users` roster), an admin **Sessies** screen (list + status filter +
|
||||
create/edit form + row actions), Stop/Annuleer on the Live cards, and a worker stopwatch
|
||||
that polls (15 s) and reconciles to server truth (converges within ~15 s when an admin
|
||||
stops/cancels/edits a session — no stuck state). **3b remaining:** reports/export
|
||||
(all-users filtered CSV — current `/api/export` is self-scoped) and user management
|
||||
(better-auth `/api/auth/admin/*`).
|
||||
Activity management (add/edit/delete handelingen + their `insole_types`) was removed from the
|
||||
worker client in the Phase 2 follow-up because it is admin-only; it must be **ported here**. The
|
||||
backend already exists (`/api/activities` writes are admin-gated; `useActivities`/the legacy
|
||||
|
||||
98
docs/sessions/2026-06-17-phase-3b1-manual-sessions.md
Normal file
98
docs/sessions/2026-06-17-phase-3b1-manual-sessions.md
Normal 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/*`.
|
||||
Reference in New Issue
Block a user