diff --git a/ui/src/components/RoutineRunVariablesDialog.test.tsx b/ui/src/components/RoutineRunVariablesDialog.test.tsx index 299be6c67b..b9c337e107 100644 --- a/ui/src/components/RoutineRunVariablesDialog.test.tsx +++ b/ui/src/components/RoutineRunVariablesDialog.test.tsx @@ -393,7 +393,15 @@ describe("RoutineRunVariablesDialog", () => { ); }); - for (let i = 0; i < 10 && !document.querySelector('[data-testid="workspace-card"]'); i += 1) { + // The workspace card mounts once experimental settings resolve, then reports its + // branch name through an effect callback. That callback triggers a follow-up render, + // so wait for the branch value itself to land — not merely for the card to appear — + // otherwise we assert against the intermediate render before the branch propagates. + const hasBranchInput = () => + Array.from(document.querySelectorAll("input")).some( + (input) => input.value === "pap-1634-routine-branch", + ); + for (let i = 0; i < 20 && !hasBranchInput(); i += 1) { await settleEffects(); } diff --git a/ui/src/components/ScheduleEditor.tsx b/ui/src/components/ScheduleEditor.tsx index bd542d2664..0fac47371b 100644 --- a/ui/src/components/ScheduleEditor.tsx +++ b/ui/src/components/ScheduleEditor.tsx @@ -266,7 +266,13 @@ export function ScheduleEditor({ onChange={(e) => { const nextCron = e.target.value; setCustomCron(nextCron); - if (getScheduleCronValidation(nextCron).valid) { + // Report validity synchronously with the keystroke so consumers can gate + // their submit affordance in the same render. Relying solely on the + // effect below leaves a one-tick window where an invalid draft still + // reads as valid to the parent. + const nextValidation = getScheduleCronValidation(nextCron); + onValidityChange?.(nextValidation.valid); + if (nextValidation.valid) { emitChange("custom", hour, minute, dayOfWeek, dayOfMonth, nextCron); } }}