diff --git a/artifacts/tx-os/src/components/executive-meetings/upcoming-meeting-alert.tsx b/artifacts/tx-os/src/components/executive-meetings/upcoming-meeting-alert.tsx
index 9f042832..f65cef3f 100644
--- a/artifacts/tx-os/src/components/executive-meetings/upcoming-meeting-alert.tsx
+++ b/artifacts/tx-os/src/components/executive-meetings/upcoming-meeting-alert.tsx
@@ -958,7 +958,10 @@ function CascadePromptBlock({
ellipsis instead of pushing the times off-screen, and
the From → To column always fits on one line. */}
-
+ {/* w-20 leaves room for the Arabic header "رقم الاجتماع"
+ with whitespace-nowrap; w-12 was too tight at common
+ font sizes. */}
+
diff --git a/artifacts/tx-os/tests/executive-meetings-upcoming-alert.spec.mjs b/artifacts/tx-os/tests/executive-meetings-upcoming-alert.spec.mjs
index 6e993ec7..58118b89 100644
--- a/artifacts/tx-os/tests/executive-meetings-upcoming-alert.spec.mjs
+++ b/artifacts/tx-os/tests/executive-meetings-upcoming-alert.spec.mjs
@@ -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,
}) => {