Finalize the pause-accounting + reorderable-handelingen + login-tab-fix feature: session log (goal/work/verification/outcome), a one-line roadmap status note, and an oxfmt pass over the changed files that strips a stray trailing comma after the last call argument in the worker Stopwatch (es5 trailing-comma style) — pure formatting, tests stay green.
6.1 KiB
Session: 2026-06-17 — Pause accounting + reorderable handelingen + login-tab fix
Goal
Ship four maintainer-reported items grouped around one root change (server-authoritative pause):
- Admin shows paused sessions as running — pause was client-only, so the admin live
view (filters
status='active') could not tell paused from running. - Reorderable handelingen — admin sets the order with ↑/↓ arrows; the worker picker follows.
- Saved duration ignored pause — stop stored wall-clock
(end − start), but the stopwatch displayed worked time. Now save worked time and store paused time too. - Wrong default tab after re-login — logout on the Account tab left the URL at
/account, so the next login re-mounted there instead of the Stopwatch.
Spec: docs/superpowers/specs/2026-06-17-pause-reorder-loginfix-design.md;
plan: docs/superpowers/plans/2026-06-17-pause-reorder-loginfix.md.
Work done
Implemented task-by-task per the plan (TDD throughout), one commit per task:
- Task 1 — Contracts + schema + migration + mappers (
0d82b6e).@solelog/shared:WorkSessiongainspaused_seconds: number+paused_at: string | null;Activitygainssort_order: number; newReorderActivitiesInput. Schema:work_sessionspaused_seconds(int NOT NULL default 0) +paused_at(timestamp_ms nullable);activities.sort_order(int NOT NULL default 0). Migration0003generated viadb:generateand applied.toWorkSession/toActivitymap the new fields. - Task 2 — Pause/resume endpoints + stop math + CSV column (
974ecb1).POST /api/sessions/:id/pause(active + not paused → setpaused_at, else 409),POST /api/sessions/:id/resume(paused → accumulatepaused_seconds, clearpaused_at, else 409).stopfolds any open pause span intopaused_seconds, thenduration_seconds = max(0, wall − paused_seconds)./api/exportgains aPaused Durationcolumn (afterTotal Duration). - Task 3 — Orderable activities + reorder endpoint (
56e0162).GET /api/activitiesorders by(sort_order, name);POSTappends withmax(sort_order)+1;PUT /api/activities/reorder(admin-gated, registered before:idroutes) assignssort_order = index, validates the id set (unknown/missing → 400), and returns the reordered list. - Task 4 — Worker Stopwatch server pause + recovery (
ce396ec).usePauseSession/useResumeSessionhooks (invalidate['sessions']); tapping the display calls pause/resume; the local clock stays for snappy feel but the server is source of truth. Recovery-on-load seedsisPaused/pausedMs/pauseStartedMsfrompaused_at/paused_seconds. - Task 5 — Worker History paused line + login-tab fix (
1765f40). History card shows a grey "Pauze H:MM:SS" pill whenpaused_seconds > 0.AuthContext.signOutdoeswindow.history.replaceState(null, '', '/')before clearing auth so the next login lands on the Stopwatch. - Task 6 — Admin Live paused state + login-tab fix (
0b0a6bd). A "Gepauzeerd" badge whenpaused_atis set; the elapsed timer freezes (worked =(paused_at − start) − paused_seconds) and a paused total is shown. AdminAuthContext.signOutgets the same path reset (lands on Live). - Task 7 — Admin Activities ↑/↓ reorder (
e48df48).useReorderActivities()→PUT /api/activities/reorder. Each non-editing row gets ↑/↓ buttons (aria-labels "Verplaats omhoog/omlaag"), disabled at the ends, swapping with the neighbour and firing the mutation. - Task 8 — Docs, lint, verification (this task). Lint/format on the feature files, full green matrix, an in-process live smoke, and this session log + roadmap note.
Verification (Task 8)
npx oxlint— clean (exit 0).npx oxfmton the feature-changed files only — reformatted two Task 4 files (apps/worker/src/screens/Stopwatch.tsx+.test.tsx) that carried a stray trailing comma after the last call argument (es5 trailing-comma style strips it); pure formatting, worker tests + typecheck stay green afterward. All other feature files were already clean.yarn workspace @solelog/api typecheck— pass;test— 60 passed (12 files).yarn workspace @solelog/worker typecheck— pass;test— 28 passed (8 files);build— pass (vite, 91 modules).yarn workspace @solelog/admin typecheck— pass;test— 21 passed (5 files);build— pass (vite, 89 modules).- Live smoke — driven in-process (
createApp()+app.request) against a real on-disk SQLite file freshly migrated to0003; no server started, so port 3000 was never bound (avoids the Windows libsql lock trap). Worker: start → pause (paused_atset, still active) → resume (paused_seconds = 2,paused_atcleared) → stop (duration_seconds = 2,paused_seconds = 2, wall ≈ 4;duration + paused ≈ wallandduration < wall). Admin:PUT /api/activities/reorder→GET /api/activitiesreflects the new order; a worker reorder → 403. The smoke script + temp DB were deleted afterward; port 3000 confirmed free.
Outcome
The feature is implemented and green across all three workspaces. Pause is now
server-authoritative: the admin live view shows a "Gepauzeerd" badge with a frozen timer,
the stored duration_seconds is worked time (paused time stored separately and surfaced in
the worker History and the CSV Paused Duration column), admins reorder handelingen with
↑/↓ arrows (the worker picker inherits the order), and logging out resets the route to /
in both clients so the next login lands on Stopwatch (worker) / Live (admin).
The two unrelated working-tree edits to .env.prod.example / docker-compose.prod.yml
(deploy SQLite bind-mount config) were left untouched — out of this feature's scope.
Next
- Phase 3b inherits the paused fields through
/api/admin/sessions(viatoWorkSession): the all-sessions list / reports view should show worked + paused per the design. - Admin pause/resume/stop of another worker's session remains Phase 3b (manual-entry / admin-control work); pause stayed a worker action here.