2026-09-08: day-of schedule change → Darius, on portal confirmation. After a same-day reschedule on the portal calendar (hour-grid drag or Move Appointment), CalendarPage classifies the change in ET (moved / added / removed; same instant = no prompt) and shows FieldRouteChangePrompt with the exact NOTICE. Confirm → POST /portal/calendar/field-schedule-change (admin/owner) → services/field_schedule_change.send_day_of_change: SMS to Darius via notify.send_sms (deliberate SMS lane, not the critical fallback; opt-out + 15/hr cap honored) with the change + new stop order, then field: one NOTICE post (“YOUR 3 PM APPT. HAS MOVED TO 11:30 AM”) followed by every stop card again in time order, renumbered, moved card marked ”❗ moved from 3 PM” (cards re-mint the stop threads). Route rebuilt from daily_schedule.build_daily_inspections (shared with the 6 AM push). Refused before any send: Dustin-assigned visits, desk reviews, non-day-of changes; 2-min Redis dedup per visit+new_start; lane refused → bulk notify(‘darius’) fallback. Nothing fires on the write itself. entirely-api 5b2380e, entirely-portal 49cde73. Not yet exercised end-to-end with a real send.
2026-10-02: FIRST real end-to-end send. Dustin (Claude Code chat) moved MAN-9251 (433 Port Dr, Lusby, Calvert County; Darius, Initial Inspection) from Wed 10/07 1:00 PM to today 2:00 PM ET at 13:16 ET. Executed in-container as the two portal routes’ service calls: supabase_write_service.update_site_visit(SV_SCHEDULED_DATE via config.ss_date_field, rush_check False) then field_schedule_change.send_day_of_change — kind=added, scope=today, NOTICE “NEW 2 PM APPT. ADDED TODAY”, Telnyx SMS to Darius ok (1 part), field notice + 3 stop cards re-posted (Darius’s full day, moved card marked, 0 failed). Audit rows under user ‘claude’ (drag_reschedule + field_schedule_change) with requested_by Dustin in metadata. Recipe: a chat-requested move needs no portal JWT — replay the routes’ service calls in-container, verify the read-back before the notify leg.
2026-10-05: inspector reassignment joins this module (entirely-api e0016a8, deployed 07:27 ET). PATCH /portal/projects/{id}/site-visits/{visit_id} {assigned_tech} (owner/admin) → services/field_schedule_change.send_reassignment fires ON THE WRITE (Dustin: “any changes should notify the affected inspectors in slack field channel”; unlike the time-change flow there is no prompt): ONE field NOTICE addressed to both inspectors (”❗ @Darius @Dustin - NOTICE: TODAY’S 11:30 AM APPT. REASSIGNED FROM DARIUS TO DUSTIN ❗ / () · Dustin takes this stop from Darius · updated route follows”), then today’s whole board re-posted grouped per inspector with the new inspector’s block first and the card marked “reassigned from Darius”; tomorrow after the 7:30 PM preview → the preview again (whole day, both tagged); any other upcoming day → the NOTICE alone. Silent (write lands, response says why): same inspector, past date, completed/cancelled/no-show, desk review, tentative. Unassigned old tech counts as Darius. No SMS leg. Lane off/refused → each inspector gets the NOTICE + his own list via his notify target (_repost_route / _repost_tomorrow_preview fallback_lists). 2-min dedup per visit + new tech. Tests tests/test_site_visit_reassign.py (55). First real send not yet exercised.
2026-10-05 (entirely-portal a12fec1): the Route Board’s Apply withholds the FieldRouteChangePrompt when the apply-move response says the hand-off NOTICE already covered that board (visitReassign.reassignCoversRouteChange: ok or deduplicated, not skipped, same scope today/tomorrow) — the combined reassignment NOTICE carries the new time and the board was re-posted under it, so send_day_of_change would only repeat it (and page Darius by SMS). Same-inspector moves, skipped or failed notices keep the prompt as before. Provisional, for Dustin to confirm (alternative: both notices fire — drop the guard).