2026-07-17 — Substitute matching on declared availability
What shipped
Section titled “What shipped”- XP-50’s substitute-candidate query (previously “any unbooked tutor”) now also requires the tutor’s declared XP-129 weekly availability to fully cover the sesi’s WIB weekday + time window, on top of the existing no-conflict predicate.
- New
availability_recordedboolean on the substitute response (public oRPC surface) + a distinctsubstitute__list__no-availabilityempty state so the operator can tell “nobody free here” from “nobody has filled availability yet”. - Dialog + empty-state copy reworded to state the true filter (aktif + menyatakan ketersediaan + tidak punya sesi bentrok).
Key decisions
Section titled “Key decisions”- Coverage = containment, not overlap. Resolve the sesi’s UTC
scheduled_atto WIB wall-clock (AT TIME ZONE 'Asia/Jakarta'+EXTRACT(ISODOW/EPOCH)), build a seconds-from-midnightnumrange, and require XP-129’s IMMUTABLEtutor_availability_time_range(...) @>it. Partial coverage correctly fails; a tutor with no slot is absent (absence is not a wildcard). No second tz convention invented — the codebase is single-tz WIB. availability_recordedis a coarse system-wideLIMIT 1flag, not per-slot — the cheapest correct day-zero rollout signal (ponytail ceiling documented).- Extended XP-50’s resolver in place (no fork), per ticket constraint.
Gotchas / lessons
Section titled “Gotchas / lessons”- Because candidates now require declared availability, the list is empty for every sesi until the XP-129 roster is populated (data entry is XP-130, out of scope here). The no-availability empty state makes this visible in-product so the operator isn’t surprised — carried into the delivery note.
apps/web/src/lib/api-client.ts’sSubstituteCandidatesResponseis a hand-maintained mirror, NOT contract-derived — had to update it explicitly alongside the contract schema.
Reference changes
Section titled “Reference changes”No locked doc was edited. New path-scoped rule added:
.claude/rules/wib-instant-vs-recurring-availability.md (matching a concrete
instant against a recurring weekly WIB window via AT TIME ZONE + numrange @>).
.claude/skills/playwright-local-e2e updated with the ephemeral-podman-PG
guidance for UX passes that seed real fixture data.