Skip to content

2026-07-17 — Substitute matching on declared availability

  • 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_recorded boolean on the substitute response (public oRPC surface) + a distinct substitute__list__no-availability empty 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).
  • Coverage = containment, not overlap. Resolve the sesi’s UTC scheduled_at to WIB wall-clock (AT TIME ZONE 'Asia/Jakarta' + EXTRACT(ISODOW/EPOCH)), build a seconds-from-midnight numrange, and require XP-129’s IMMUTABLE tutor_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_recorded is a coarse system-wide LIMIT 1 flag, 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.
  • 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’s SubstituteCandidatesResponse is a hand-maintained mirror, NOT contract-derived — had to update it explicitly alongside the contract schema.

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.