1f84109bd9
Floating, draggable alert that appears on every Tx OS page when an
Executive Meeting is within five minutes of starting.
Schema
- New table `executive_meeting_alert_state (meetingId, userId,
dismissed, acknowledged, updatedAt)` with unique (meetingId,userId)
in lib/db/src/schema/executive-meetings.ts. Apply via
`pnpm --filter @workspace/db run push-force` per environment.
API (artifacts/api-server/src/routes/executive-meetings.ts)
- GET /executive-meetings/alert-state?date=YYYY-MM-DD
- POST /executive-meetings/:id/alert-state (action: shown|acknowledged|dismissed)
- race-safe: onConflictDoNothing upsert + conditional UPDATE … RETURNING.
- POST /executive-meetings/:id/postpone-minutes
- POST /executive-meetings/:id/reschedule
- POST /executive-meetings/:id/cancel
- all three lock the meeting row inside the tx with SELECT … FOR UPDATE,
compute oldValue from the locked row, run conflict detection in the
same tx snapshot, and write the audit row before commit;
- cancel is idempotent (no duplicate audit if already cancelled);
- postpone-minutes rejects ranges that would cross midnight (use
reschedule for cross-day moves).
- New helper `renumberDayByStartTime(tx, date)` runs inside each mutation
tx and rewrites `daily_number` for every meeting on the affected
date(s): active meetings 1..N by start_time, cancelled meetings
pushed to the tail. Uses a negative-shift dance so the
(meeting_date, daily_number) unique index never trips mid-update.
Reschedule renumbers both the old and new date when the day moves.
- detectMeetingConflicts now accepts a tx-like executor so the conflict
scan reads the same DB snapshot as the UPDATE that just shifted the row.
Frontend
- New component artifacts/tx-os/src/components/executive-meetings/
upcoming-meeting-alert.tsx — globally mounted in App.tsx inside
AuthProvider. Draggable with localStorage position persistence,
RTL-aware, polls every 30 s, shows start–end window, postpone-by-
minutes chips [5,10,15,30,45,60], full reschedule sub-form, and a
cancel-meeting flow that requires an explicit confirm step before
the destructive call fires.
- Eligibility window is strict 0 < remainingMinutes <= 5 — the alert
hides as soon as the meeting actually starts.
- Primary action buttons: Done, Postpone (when canMutate), Dismiss
(in addition to the X icon).
- artifacts/tx-os/src/pages/executive-meetings.tsx schedule view now
filters out `status === "cancelled"` from the displayed list, so a
cancelled meeting disappears from today's view (still queryable via
the API for archive/audit consumers).
i18n
- New `executiveMeetings.alert.*` keys in both en.json and ar.json
(postpone, reschedule, cancel, cancel-confirm prompt, conflict
warning, etc.).
Tests
- artifacts/tx-os/tests/executive-meetings-upcoming-alert.spec.mjs —
8 Playwright scenarios, all green:
1. Done acknowledges the alert
2. Postpone-10 shifts both start and end and clears the alert
3. Cancel-with-confirm marks the meeting cancelled
4. Dismiss (X) writes a dismissed audit row
5. Postpone-chip-10 + conflict-warning toast
6. Reschedule to a different day clears today's alert
7. Cancel removes the meeting from today's schedule and renumbers
the survivor
8. Arabic locale renders the RTL alert with Arabic title
Docs
- replit.md updated with the new table, routes, and migration step.
Pre-existing tsc errors at lines 546, 662, and 2107 of
executive-meetings.ts are unrelated to this task.