75fa714b25
Backend (artifacts/api-server/src/routes/executive-meetings.ts):
- New helpers computeCascadeShift + applyCascadeShift; new schema
cascadePreviewSchema; cascadeFollowing flag on postpone-minutes
and reschedule.
- POST /executive-meetings/:id/cascade-preview returns followers +
blockedBy.
- Writer paths now SELECT followers FOR UPDATE inside the txn so
concurrent mutations cannot lose updates and audit oldStart/oldEnd
always reflect the locked-current values (architect feedback).
- Reschedule only cascades when same date AND newStart > oldStart.
- Midnight rejection rolls back the primary too; named offender is
surfaced verbatim to the UI.
- One meeting_cascade_shift audit per follower (trigger meeting +
delta in the row for replay).
Frontend (artifacts/tx-os/src/components/executive-meetings/
upcoming-meeting-alert.tsx):
- CascadePromptBlock with loading / blocked / normal variants and
data-testids cascade-prompt[-blocked|-loading], cascade-keep-times,
cascade-shift-following, cascade-back, cascade-follower-{id}.
- runCascadePreview helper with in-flight + busy guard against
duplicate submissions; falls through to single-meeting submit when
no followers and not blocked.
- rescheduleSubmit extracted; cascade_crosses_midnight from server
is caught and re-rendered as the blocked variant.
i18n: cascade* keys under executiveMeetings.alert in en.json + ar.json
(Arabic plural zero/one/two/few/many/other for header + shift action).
ALSO added `executiveMeetings.audit.action.meeting_cascade_shift` in
both locales so the History view renders a localized label instead of
falling back to the raw action code (review feedback).
Tests:
- 8 cascade backend tests in executive-meetings.test.mjs (preview
shape, atomic shift, skip cancelled/completed, midnight rollback,
reschedule delta + no-op cases) all pass.
- New e2e "Reschedule cascade — opting in shifts all later same-day
meetings" (API-driven backend contract).
- NEW UI dialog test "Cascade prompt UI: Shift sends
cascadeFollowing=true; Keep sends false" — opens the postpone
confirm, mocks cascade-preview to a deterministic single-follower
response, then verifies (via request interception of
/postpone-minutes) that Shift sends cascadeFollowing=true and Keep
sends cascadeFollowing=false (review feedback).
- NEW UI test "Cascade prompt UI: no followers => prompt skipped,
direct submit with cascadeFollowing=false" — locks in the no-
followers branch from the second review pass: mocks cascade-
preview to {followers:[], blockedBy:null} and asserts the prompt
never renders and the postpone-minutes payload carries
cascadeFollowing=false. Both UI tests use wildcard regex routes so
same-day pollution from prior tests cannot misroute requests.
- Made two existing tests (Postpone by 10 minutes, conflict warning)
pollution-tolerant by polling for either the cascade prompt or the
meeting-postponed audit row.
Pre-existing flaky tests (notifications fan-out, postpone race, row
color realtime, reorder, status transitions, alert-done/close
realtime, reschedule-different-day visibility, cancel/renumber strict
mode) are not related to this work and were not modified.
Drift note: prior commit picked up an incidental opengraph.jpg byte
diff (25906 -> 25929 bytes) from the tx-os web workflow restart — no
content change visible to this task; left in place as removing it
requires a destructive git operation.