@@ -328,21 +328,33 @@ async function handleSkipOrError(
328328 "getUserData: input rejected, retrying" ,
329329 )
330330
331- // `retryMessage` defaults to "" (schema has no minimum), and an empty
332- // prompt is silently dropped by sendChatMessage — the contact would see
333- // nothing at all on an invalid reply. Fall back to the step's main
334- // message whenever retryMessage is blank, so the retry always re-prompts
335- // (and, for date/datetime on capable channels, re-offers the picker
336- // button).
337331 await sendMessage (
338332 props ,
339- step . retryMessage . trim ( ) || step . message ,
333+ resolveRetryPromptText ( step ) ,
340334 ( ( ctx ?. variables . conversation . challengeAttempts ?. value as number ) ?? 1 ) + 1 ,
341335 )
342336
343337 return { result : undefined , status : "retry" }
344338}
345339
340+ /**
341+ * `retryMessage` defaults to "" (schema has no minimum) and an empty prompt
342+ * is silently dropped by the send path. For the webview formats
343+ * (date/datetime) a blank retry falls back to the step's main message so the
344+ * retry always re-offers the picker button — a silent retry would strand the
345+ * contact with no way back to the picker. Every other reply format keeps the
346+ * long-standing behavior (blank retry sends nothing): flows built before
347+ * this feature may rely on that silence, and typed input still works there.
348+ */
349+ function resolveRetryPromptText ( step : GetUserDataStepSchema ) : string {
350+ const isWebviewFormat = Boolean (
351+ DATE_TIME_WEBVIEW_MODE_BY_REPLY_FORMAT [ step . replyFormat ] ,
352+ )
353+ return isWebviewFormat
354+ ? step . retryMessage . trim ( ) || step . message
355+ : step . retryMessage
356+ }
357+
346358async function validateUserData (
347359 props : ExecuteStepProps < GetUserDataStepSchema > ,
348360) : Promise < ReplyValidationResult > {
0 commit comments