@@ -104,6 +104,55 @@ type ConsumeArchivedAnswerPreviewParams = {
104104export function createLaneTextDeliverer ( params : CreateLaneTextDelivererParams ) {
105105 const getLanePreviewText = ( lane : DraftLaneState ) => lane . lastPartialText ;
106106
107+ const shouldSkipRegressivePreviewUpdate = ( args : {
108+ currentPreviewText : string | undefined ;
109+ text : string ;
110+ skipRegressive : "always" | "existingOnly" ;
111+ hadPreviewMessage : boolean ;
112+ } ) : boolean =>
113+ Boolean ( args . currentPreviewText ) &&
114+ args . currentPreviewText . startsWith ( args . text ) &&
115+ args . text . length < args . currentPreviewText . length &&
116+ ( args . skipRegressive === "always" || args . hadPreviewMessage ) ;
117+
118+ const tryEditPreviewMessage = async ( args : {
119+ laneName : LaneName ;
120+ messageId : number ;
121+ text : string ;
122+ context : "final" | "update" ;
123+ previewButtons ?: TelegramInlineButtons ;
124+ updateLaneSnapshot : boolean ;
125+ lane : DraftLaneState ;
126+ treatEditFailureAsDelivered : boolean ;
127+ } ) : Promise < boolean > => {
128+ try {
129+ await params . editPreview ( {
130+ laneName : args . laneName ,
131+ messageId : args . messageId ,
132+ text : args . text ,
133+ previewButtons : args . previewButtons ,
134+ context : args . context ,
135+ } ) ;
136+ if ( args . updateLaneSnapshot ) {
137+ args . lane . lastPartialText = args . text ;
138+ }
139+ params . markDelivered ( ) ;
140+ return true ;
141+ } catch ( err ) {
142+ if ( args . treatEditFailureAsDelivered ) {
143+ params . log (
144+ `telegram: ${ args . laneName } preview ${ args . context } edit failed after stop-created flush; treating as delivered (${ String ( err ) } )` ,
145+ ) ;
146+ params . markDelivered ( ) ;
147+ return true ;
148+ }
149+ params . log (
150+ `telegram: ${ args . laneName } preview ${ args . context } edit failed; falling back to standard send (${ String ( err ) } )` ,
151+ ) ;
152+ return false ;
153+ }
154+ } ;
155+
107156 const tryUpdatePreviewForLane = async ( {
108157 lane,
109158 laneName,
@@ -122,12 +171,39 @@ export function createLaneTextDeliverer(params: CreateLaneTextDelivererParams) {
122171 const lanePreviewMessageId = lane . stream . messageId ( ) ;
123172 const hadPreviewMessage =
124173 typeof previewMessageIdOverride === "number" || typeof lanePreviewMessageId === "number" ;
125- if ( stopBeforeEdit ) {
126- if ( ! hadPreviewMessage && context === "final" ) {
127- // If debounce prevented the first preview, replace stale pending partial text
128- // before final stop() flush sends the first visible preview.
129- lane . stream . update ( text ) ;
174+ const stopCreatesFirstPreview = stopBeforeEdit && ! hadPreviewMessage && context === "final" ;
175+ if ( stopCreatesFirstPreview ) {
176+ // Final stop() can create the first visible preview message.
177+ // Prime pending text so the stop flush sends the final text snapshot.
178+ lane . stream . update ( text ) ;
179+ await params . stopDraftLane ( lane ) ;
180+ const previewMessageId = lane . stream . messageId ( ) ;
181+ if ( typeof previewMessageId !== "number" ) {
182+ return false ;
130183 }
184+ const currentPreviewText = previewTextSnapshot ?? getLanePreviewText ( lane ) ;
185+ const shouldSkipRegressive = shouldSkipRegressivePreviewUpdate ( {
186+ currentPreviewText,
187+ text,
188+ skipRegressive,
189+ hadPreviewMessage,
190+ } ) ;
191+ if ( shouldSkipRegressive ) {
192+ params . markDelivered ( ) ;
193+ return true ;
194+ }
195+ return tryEditPreviewMessage ( {
196+ laneName,
197+ messageId : previewMessageId ,
198+ text,
199+ context,
200+ previewButtons,
201+ updateLaneSnapshot,
202+ lane,
203+ treatEditFailureAsDelivered : true ,
204+ } ) ;
205+ }
206+ if ( stopBeforeEdit ) {
131207 await params . stopDraftLane ( lane ) ;
132208 }
133209 const previewMessageId =
@@ -138,34 +214,26 @@ export function createLaneTextDeliverer(params: CreateLaneTextDelivererParams) {
138214 return false ;
139215 }
140216 const currentPreviewText = previewTextSnapshot ?? getLanePreviewText ( lane ) ;
141- const shouldSkipRegressive =
142- Boolean ( currentPreviewText ) &&
143- currentPreviewText . startsWith ( text ) &&
144- text . length < currentPreviewText . length &&
145- ( skipRegressive === "always" || hadPreviewMessage ) ;
217+ const shouldSkipRegressive = shouldSkipRegressivePreviewUpdate ( {
218+ currentPreviewText,
219+ text,
220+ skipRegressive,
221+ hadPreviewMessage,
222+ } ) ;
146223 if ( shouldSkipRegressive ) {
147224 params . markDelivered ( ) ;
148225 return true ;
149226 }
150- try {
151- await params . editPreview ( {
152- laneName,
153- messageId : previewMessageId ,
154- text,
155- previewButtons,
156- context,
157- } ) ;
158- if ( updateLaneSnapshot ) {
159- lane . lastPartialText = text ;
160- }
161- params . markDelivered ( ) ;
162- return true ;
163- } catch ( err ) {
164- params . log (
165- `telegram: ${ laneName } preview ${ context } edit failed; falling back to standard send (${ String ( err ) } )` ,
166- ) ;
167- return false ;
168- }
227+ return tryEditPreviewMessage ( {
228+ laneName,
229+ messageId : previewMessageId ,
230+ text,
231+ context,
232+ previewButtons,
233+ updateLaneSnapshot,
234+ lane,
235+ treatEditFailureAsDelivered : false ,
236+ } ) ;
169237 } ;
170238
171239 const consumeArchivedAnswerPreviewForFinal = async ( {
0 commit comments