#485 Fix overlapping layout in the Postpone meeting dialog
In Arabic/RTL the Postpone dialog (opened from the upcoming-meeting
alert) was visibly overlapping its own borders on two tabs:
1. Postpone tab → cascade preview ("الاجتماعات المتأثرة"): the times
column ran wider than the pink panel, the title column pushed it
off-screen, and the meeting-# header wrapped to two lines while
the others stayed on one.
2. Reschedule tab: three native date / start / end inputs forced into
md:grid-cols-3 inside an sm:max-w-md dialog, which clipped the
right-most input against the dialog frame in RTL.
Both issues were pure layout — the dialog was simply too narrow for
its content.
Changes (artifacts/tx-os/src/components/executive-meetings/upcoming-meeting-alert.tsx):
- Bumped the postpone DialogContent from `sm:max-w-md` (≈448 px) to
`sm:max-w-2xl` (≈672 px). Still full width on phones, capped on
desktop. Reschedule's existing `grid-cols-1 sm:grid-cols-2
md:grid-cols-3` now has room to actually render three inputs at md
without clipping (Input already defaults to w-full).
- CascadePromptBlock affected-meetings table: switched to
`table-fixed` with an explicit `<colgroup>` (w-20 number / flex
title / w-40 times). The number column was bumped from w-12 to
w-20 so the Arabic header "رقم الاجتماع" + `whitespace-nowrap`
has room at common font sizes (raised during code review). Title
cell keeps its `truncate` + `title=` tooltip but no longer needs
`max-w-[180px]`. Added `whitespace-nowrap` to # and times headers
so the latter never wraps. Wrapper got `overflow-x-hidden` to
belt-and-suspenders the no-horizontal-scroll guarantee. Action
button row already used `flex-wrap` — preserved.
Tests (artifacts/tx-os/tests/executive-meetings-upcoming-alert.spec.mjs):
- Added a Reschedule-tab regression test that opens the dialog at
1280×800 in Arabic and asserts the bounding boxes of
`reschedule-date` / `reschedule-start` / `reschedule-end` all sit
inside the dialog's bounding box (1 px tolerance).
- Added a Cascade-table regression test (raised during code review)
that seeds a primary + 2 followers, postpones to fire the cascade
prompt, and asserts the `cascade-followers-list` and the last
`<td>` (times) of the first follower row both sit inside the
dialog at desktop width in RTL.
- Confirmed no behavior regressions across the postpone-by-10,
cascade follower display, tab-switching, and reschedule-to-tomorrow
tests.
Out of scope (per task spec): backend cascade/postpone logic, the
floating alert panel itself, dialog visual restyling, and the Cancel
tab beyond what comes "for free" with the wider dialog.
The pre-existing `test` workflow failures (executive-meetings reorder,
font-settings roundtrip, notes-share recipient PATCH 403/404) are
unrelated to this layout change and were already failing before.
This commit is contained in:
@@ -958,7 +958,10 @@ function CascadePromptBlock({
|
||||
ellipsis instead of pushing the times off-screen, and
|
||||
the From → To column always fits on one line. */}
|
||||
<colgroup>
|
||||
<col className="w-12" />
|
||||
{/* w-20 leaves room for the Arabic header "رقم الاجتماع"
|
||||
with whitespace-nowrap; w-12 was too tight at common
|
||||
font sizes. */}
|
||||
<col className="w-20" />
|
||||
<col />
|
||||
<col className="w-40" />
|
||||
</colgroup>
|
||||
|
||||
@@ -1291,6 +1291,84 @@ test("Upcoming-meeting alert: details panel renders attendees as numbered tables
|
||||
await expect(secondGroupRows.first()).toContainText("Internal One");
|
||||
});
|
||||
|
||||
test("Postpone dialog: cascade-followers table sits fully inside the dialog (no horizontal spill, RTL)", async ({
|
||||
page,
|
||||
}) => {
|
||||
// #485: regression for the cascade preview overlapping the dialog
|
||||
// edge in Arabic. Seed a primary meeting + two followers later in
|
||||
// the same day so the cascade prompt definitely fires after
|
||||
// confirming a postpone, then assert the followers-list bounding
|
||||
// box sits within the dialog's bounding box.
|
||||
await setLang(page, "ar");
|
||||
const primary = await insertImminentMeeting({
|
||||
titleAr: `${TEST_TAG} cascade-layout primary AR ${"بعنوان طويل جداً يكفي لاختبار القص بالنقاط في عمود العنوان داخل اللوحة"}`,
|
||||
titleEn: `${TEST_TAG} cascade-layout primary EN with a sufficiently long title to verify column truncation`,
|
||||
deltaMins: 3,
|
||||
});
|
||||
await insertImminentMeeting({
|
||||
titleAr: `${TEST_TAG} cascade-layout follower-1 AR`,
|
||||
titleEn: `${TEST_TAG} cascade-layout follower-1 EN`,
|
||||
deltaMins: 30,
|
||||
});
|
||||
await insertImminentMeeting({
|
||||
titleAr: `${TEST_TAG} cascade-layout follower-2 AR`,
|
||||
titleEn: `${TEST_TAG} cascade-layout follower-2 EN`,
|
||||
deltaMins: 60,
|
||||
});
|
||||
await page.setViewportSize({ width: 1280, height: 800 });
|
||||
await loginViaUi(page, "admin", "admin123");
|
||||
await page.goto("/");
|
||||
await expect(page.getByTestId("upcoming-meeting-alert")).toBeVisible({
|
||||
timeout: 15_000,
|
||||
});
|
||||
await page.getByTestId("alert-postpone").click();
|
||||
const dialog = page.getByTestId("postpone-dialog");
|
||||
await expect(dialog).toBeVisible();
|
||||
await page.getByTestId("postpone-chip-10").click();
|
||||
await expect(page.getByTestId("postpone-confirm-block")).toBeVisible();
|
||||
await page.getByTestId("postpone-confirm-yes").click();
|
||||
|
||||
// Cascade prompt must appear with this seeding (two later meetings
|
||||
// on the same day). Geometry checks then verify no spill.
|
||||
const followersList = page.getByTestId("cascade-followers-list");
|
||||
await expect(followersList).toBeVisible({ timeout: 8_000 });
|
||||
|
||||
const dialogBox = await dialog.boundingBox();
|
||||
const tableBox = await followersList.boundingBox();
|
||||
expect(dialogBox).not.toBeNull();
|
||||
expect(tableBox).not.toBeNull();
|
||||
// Table must sit fully inside the dialog (1px tolerance for
|
||||
// sub-pixel rounding).
|
||||
expect(tableBox.x + 1).toBeGreaterThanOrEqual(dialogBox.x);
|
||||
expect(tableBox.x + tableBox.width).toBeLessThanOrEqual(
|
||||
dialogBox.x + dialogBox.width + 1,
|
||||
);
|
||||
|
||||
// The truncating title cell must not push the times column out of
|
||||
// the dialog. Check the first follower row's last cell (times).
|
||||
const lastCell = page
|
||||
.locator('[data-testid^="cascade-follower-"]')
|
||||
.first()
|
||||
.locator("td")
|
||||
.last();
|
||||
const lastBox = await lastCell.boundingBox();
|
||||
expect(lastBox).not.toBeNull();
|
||||
expect(lastBox.x + lastBox.width).toBeLessThanOrEqual(
|
||||
dialogBox.x + dialogBox.width + 1,
|
||||
);
|
||||
|
||||
// Politely back out so this seeded meeting doesn't preempt later
|
||||
// tests' alerts.
|
||||
await page.getByTestId("cascade-back").click();
|
||||
await pool.query(
|
||||
`UPDATE executive_meetings
|
||||
SET start_time = (start_time::time + interval '2 hours')::time,
|
||||
end_time = (end_time::time + interval '2 hours')::time
|
||||
WHERE id = $1`,
|
||||
[primary.id],
|
||||
);
|
||||
});
|
||||
|
||||
test("Postpone dialog: reschedule date / start / end inputs sit fully inside the dialog (no clipping at desktop width, RTL)", async ({
|
||||
page,
|
||||
}) => {
|
||||
|
||||
Reference in New Issue
Block a user