Task #152: cell-merge + current-meeting tint on EM schedule

Adds two related features to the executive-meetings schedule:

1. Current-meeting tinting — meeting/attendees/time cells of the row
   matching the current time get a soft wash of the user's highlight
   color (~13% alpha), keeping their original text color and the
   existing colored ring. The # cell stays solid white/red.

2. Cell-merge overlay — editors can merge "meeting+attendees",
   "meeting+attendees+time", or the whole row into a single free-text
   cell. Stored in the DB so all viewers see the same overlay; the
   originals (titleAr/En, attendees, start/endTime) stay untouched and
   are restored on Unmerge.

Implementation notes
- DB: 3 nullable columns added to executive_meetings
  (merge_start_column, merge_end_column, merge_text). Applied via
  direct ALTER TABLE because pnpm --filter db run push is blocked by
  the pre-existing app_permissions duplicate (Task #148, surfaced as
  the new follow-up #156). Schema file kept in sync.
- API: meetingPatchSchema accepts an optional `merge` field
  (`null | {start,end,text}`) with start<=end ordering and server-side
  sanitizeRichText on the text. Reuses the existing PATCH route.
- Frontend: new MergeMenu component + canonical-order range resolution
  so merges still render when boundary columns are hidden, and
  gracefully degrade to unmerged rendering (without losing DB state)
  when column reordering makes the visible span non-contiguous.
  Unmerge stays available whenever a stored merge exists, even in
  degraded state, and existing mergeText is preserved on re-merge.
- i18n: en + ar keys under executiveMeetings.merge.*

Out of scope (deferred to follow-ups #154 / #155, per task brief)
- API + e2e test coverage for the merge endpoint and UI flows
- Realtime emission for executive-meetings mutations (no existing
  emit on this surface; covered by #155)

Validation
- TypeScript compiles cleanly across api-server + tx-os.
- API tests: 108/110 pass — the 2 failures are the pre-existing
  app_permissions-unique cases (Task #148, follow-up #156),
  unrelated to this change.
- Architect code review approved after addressing hidden-column
  fallback, non-contiguous reorder degradation, and
  Unmerge-availability edge cases.
This commit is contained in:
riyadhafraa
2026-04-29 14:05:21 +00:00
parent 2ffd63832a
commit 539f11e25d
6 changed files with 410 additions and 13 deletions
+11
View File
@@ -33,6 +33,17 @@ export const executiveMeetingsTable = pgTable(
isHighlighted: integer("is_highlighted").notNull().default(0),
notes: text("notes"),
attachments: jsonb("attachments").default([]),
// Optional cell-merge overlay for the schedule grid. When set, the
// schedule renders one merged cell spanning columns
// [mergeStartColumn..mergeEndColumn] and shows mergeText (sanitized
// HTML) instead of the original column values. The original
// titleAr/En + attendees + start/endTime stay untouched in the DB,
// so clearing the merge restores the original cells exactly.
// Allowed values for the column ids match the frontend ColumnId
// enum: "number" | "meeting" | "attendees" | "time".
mergeStartColumn: varchar("merge_start_column", { length: 32 }),
mergeEndColumn: varchar("merge_end_column", { length: 32 }),
mergeText: text("merge_text"),
createdBy: integer("created_by").references(() => usersTable.id, {
onDelete: "set null",
}),