From e9936b8ad2c6d82ae6f8e9afcff5f46d68a34790 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Tue, 25 Aug 2026 19:58:45 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A2=20fix:=20Restore=20Schedule=20Dial?= =?UTF-8?q?og=20Scrolling=20So=20Save=20Stays=20Reachable=20(#15225)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SidePanel/Schedules/ScheduleDialog.tsx | 31 +++++-------------- e2e/specs/mock/schedules-execution.spec.ts | 16 +++++----- 2 files changed, 16 insertions(+), 31 deletions(-) diff --git a/client/src/components/SidePanel/Schedules/ScheduleDialog.tsx b/client/src/components/SidePanel/Schedules/ScheduleDialog.tsx index 51092129ee..e85fedc6c0 100644 --- a/client/src/components/SidePanel/Schedules/ScheduleDialog.tsx +++ b/client/src/components/SidePanel/Schedules/ScheduleDialog.tsx @@ -95,9 +95,8 @@ const DEFAULT_CRON = '0 9 * * 1-5'; const DEFAULT_WEEKLY_DAYS = [1]; /** Enough previewed occurrences to show the SHAPE of a cadence (that `0 9,17 * * 1-5` - * fires twice a day), which a single row cannot. Kept small deliberately: the dialog - * turns scrolling off at `md` (see the template className below), so every preview - * row spends the same fixed height budget a form row does. */ + * fires twice a day), which a single row cannot. Kept small deliberately so the + * schedule's next occurrences remain easy to scan in the dialog. */ const PREVIEW_RUN_COUNT = 3; const FORM_ID = 'schedule-form'; @@ -527,10 +526,7 @@ export default function ScheduleDialog({ [locale, weekdayIndexes], ); - /** A CELL in the cadence grid rather than a row of its own, in both modes. The - * template's height budget is a contract (see its className below): scrolling is - * off at `md`, so a full-width timezone row would push the footer's submit button - * out of a 720px-tall viewport where it can never be scrolled back. */ + /** A CELL in the cadence grid rather than a row of its own, in both modes. */ const timezoneField = (
@@ -569,17 +565,7 @@ export default function ScheduleDialog({ {/* Identity row: what the schedule is, who runs it, where its chats land. @@ -723,8 +709,7 @@ export default function ScheduleDialog({ {/* Full width, not inside the agent cell: at a third of the dialog this - sentence wraps an extra line, and the identity row is the tallest thing - competing for the fixed height budget described on the template above. */} + sentence wraps an extra line and makes the identity row needlessly tall. */}

{localize('com_ui_schedule_target_new_chat')}

@@ -825,8 +810,7 @@ export default function ScheduleDialog({
@@ -850,8 +834,7 @@ export default function ScheduleDialog({ control={control} render={({ field }) => ( // No wrap, and every pill shares the row's width equally: at - // `md` this cell is a third of the dialog, and a second pill - // line would spend height the budget above does not have. + // `md` this cell is a third of the dialog.
{weekdayOptions.map(({ day, label, narrow }) => { const selected = field.value.includes(day); diff --git a/e2e/specs/mock/schedules-execution.spec.ts b/e2e/specs/mock/schedules-execution.spec.ts index f8ec1ab739..c1612998b5 100644 --- a/e2e/specs/mock/schedules-execution.spec.ts +++ b/e2e/specs/mock/schedules-execution.spec.ts @@ -204,6 +204,7 @@ test.describe('scheduled chat execution', () => { */ test('edits a schedule through the UI with the cadence persisted', async ({ page }) => { test.setTimeout(120000); + await page.setViewportSize({ width: 1280, height: 720 }); await page.goto('/c/new', { timeout: 15000 }); const token = await getAccessToken(page); const agent = await ensureAgent(page, token); @@ -218,19 +219,20 @@ test.describe('scheduled chat execution', () => { const persisted = page.getByTestId('schedule-card').filter({ hasText: name }); await expect(persisted).toContainText(/Runs weekly/i, { timeout: 15000 }); - // EDIT through the dialog: rename and move the cadence to daily. The dialog + // EDIT through the dialog: rename and move the cadence to Custom. The dialog // pre-populates the agent from the schedule, so no picker interaction is needed. await persisted.getByRole('button', { name: 'Schedule options' }).click(); await page.getByRole('menuitem', { name: 'Edit' }).click(); const editDialog = page.getByRole('dialog'); const renamed = `${name} edited`; await editDialog.locator('#schedule-name').fill(renamed); - await editDialog.getByRole('radio', { name: 'Daily' }).click(); - // The dialog does not scroll at `md` and up, so its content has to fit the - // viewport: a field that adds a ROW pushes Save out of a 720px-tall window with - // no way to scroll it back. Asserted explicitly because the bare click below - // reports that as a 2-minute timeout on a visible, enabled button. + await editDialog.getByRole('radio', { name: 'Custom' }).click(); + await editDialog.getByTestId('schedule-cron-input').fill('0 9 * * *'); + + // Custom adds a hint and validation message. On a 720px viewport, the dialog + // itself must scroll so the footer remains reachable. const save = editDialog.getByRole('button', { name: 'Save' }); + await save.scrollIntoViewIfNeeded(); await expect(save).toBeInViewport(); await save.click(); @@ -238,7 +240,7 @@ test.describe('scheduled chat execution', () => { await openSchedulesPanel(page); const edited = page.getByTestId('schedule-card').filter({ hasText: renamed }); await expect(edited).toBeVisible({ timeout: 15000 }); - await expect(edited).toContainText(/Runs daily/i); + await expect(edited).toContainText(/Runs on cron 0 9 \* \* \*/i); }); /**