Skip to content

2026-07-17 — Tutor availability slots

  • New tutor_availability_slots table (day_of_week 1..7 ISO, start_time, duration_ms), anchored on users.id ON DELETE CASCADE, units mirroring schedules.
  • Full oRPC surface (contract tier-1/tier-2 + list/create/update/remove routes + service), audit on every mutation, gated hr:tutor:read (view) / hr:tutor:write (mutate).
  • New “Ketersediaan” tab on /admin/tutors/$id: list rows, inline add/edit form (day + native time input + duration selects), remove-confirm dialog, loading/error/empty states, inline overlap error.
  • Overlap prevention lives in the DB, not just the form: a raw-SQL EXCLUDE USING gist constraint (excl_tutor_avail_no_overlap) over a half-open numrange of seconds-from-midnight, built by an IMMUTABLE fn — mirrors the existing sesi no-overlap guards. Half-open [) so back-to-back slots don’t collide. Drizzle can’t express EXCLUDE, so it’s a generate --custom migration.
  • New table (not an extension of schedules): a slot has no student_id/subject_id, which are notNull on schedules.
  • Iter-1 admin config (admin proxy), not customer self-service — accepted in scope by review + UX driver.
  • Cross-midnight windows (start + duration > 24h) are out of iter-1 recurring-weekly scope; modelled as within-day seconds, ceiling noted with a ponytail: comment in the migration.
  • Native <input type="time"> renders a browser-chrome clock glyph that reads as a disabled/ghost icon in dark-on-light screenshots — added to frontend-visual-test.md so reviewers don’t mistake it for a disabled field.
  • The tutor detail route id (/admin/tutors/$id) is the user id; the availability API internally resolves to a different tutor_profile.id — expected, worth knowing when correlating network requests.

No locked doc was edited by this ticket, but one is now stale and flagged for reconciliation at the gate: .claude/rules/domain-glossary.md lists Slot as table tutor_slots, iter 2+. This ticket shipped it as tutor_availability_slots in iter-1. The glossary line should be updated to the real table name and iter after human review of the scope call.