2026-07-17 — Tutor availability slots
What shipped
Section titled “What shipped”- New
tutor_availability_slotstable (day_of_week 1..7 ISO, start_time, duration_ms), anchored onusers.idON DELETE CASCADE, units mirroringschedules. - 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.
Key decisions
Section titled “Key decisions”- Overlap prevention lives in the DB, not just the form: a raw-SQL
EXCLUDE USING gistconstraint (excl_tutor_avail_no_overlap) over a half-opennumrangeof 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 agenerate --custommigration. - New table (not an extension of
schedules): a slot has no student_id/subject_id, which are notNull onschedules. - Iter-1 admin config (admin proxy), not customer self-service — accepted in scope by review + UX driver.
Gotchas / lessons
Section titled “Gotchas / lessons”- 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 tofrontend-visual-test.mdso 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 differenttutor_profile.id— expected, worth knowing when correlating network requests.
Reference changes
Section titled “Reference changes”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.