- Rebrand all user-facing text to AgendaMax - Fix sidebar scrolling: card container no longer overflows html (flex h-full min-h-0) - Fix calendar toolbar hover: active buttons keep readable contrast - Sticky calendar toolbar (month/week/day always visible on scroll) - Seed guarantees 2-4 appointments per day for current week (Mon-Sat)
3.1 KiB
Task 3 Report — scheduling.ts DB functions
Files changed
server/lib/scheduling.ts— appended DB-backed functions from Task 3 plan (CandidateInfo,safeArr,getCandidates,getExistingBusy,isAvailable,rankOne,pickBestSlotEmployee,AutoAssignResult,AutoAssignCtx,autoAssign,runInTransaction) +import type { DatabaseSync } from "node:sqlite".
Typecheck
npm run typecheck → 0 errors (after the deviation below).
test:unit
npm run test:unit → 15/15 pass (pure tests unaffected).
tx smoke-test output
Temp file _tx-smoke.mjs (deleted after run). It imports runInTransaction and db, exercises happy path + throw path, and verifies a real UPDATE was rolled back.
PASS runInTransaction returns fn value (42)
PASS no active tx after commit
PASS runInTransaction rethrows on error
PASS no active tx after rollback
PASS rollback undid the UPDATE
5/5 passed
EXIT=0
PRAGMA active_transaction was used (newer node:sqlite exposes it; falls back to a no-row / 0 object) to confirm no dangling transaction after both commit and rollback. The real-state rollback check (UPDATE _tx_probe SET v=999 inside a throwing fn) proves the ROLLBACK is functional, not just emitted.
Deviations
- Plan typo fix in
autoAssign(line 256). The plan literally wrote:but insideif (!best || ranked.score > best.score || (ranked.score === best.score && c.info.id < best.info.id)) {autoAssignthe loop variablecis aCandidateInfo(no.infofield), soc.info.idis a type error (TS2339). Fixed toc.id < best.info.id.bestis still aRankedCandidatesobest.info.idis correct. The semantic intent (tie-break by lowest employee id) is preserved exactly. Note: the sibling functionpickBestSlotEmployeeusesc.idcorrectly in the plan — confirming the autoAssign line was a typo, not an intentional shape.
No other deviations. The import type { DatabaseSync } is placed mid-file (after scoreCandidate), which is valid because ES module import declarations are hoisted; tsc -b --noEmit and tsx both accept it.
Self-review
- Does
autoAssignreturnnullwhen no candidate is free? YES. Two null paths:candidates.length === 0→ immediatereturn null(line 245).- After the loop,
if (!best) return null(line 260) covers the case where every candidate was filtered out (closed that day, slot outside working window, or has a conflict).
- Does it respect per-employee working hours? YES. For each candidate it calls
getWorkingHoursForDate(c.empWh, ctx.bizWh, new Date(ctx.startMs))— employee override wins, business is the fallback, and a closed day returnsnull→ candidate iscontinued. Then the slot must satisfyctx.startMs >= wh.startMs && ctx.endMs <= wh.endMs, otherwise the candidate is skipped. - Does
runInTransactionroll back on throw? YES. Verified by smoke test: a thrown error insidefncausesdb.exec("ROLLBACK")to run, the error is re-thrown, and a realUPDATEmade inside the throwing fn is undone (the probe row retained its pre-tx value).