@@ -479,7 +479,7 @@ describe("product skin: stream + queue + overlay", () => {
479479 )
480480 } )
481481
482- test ( "Ctrl+G cancels the last queued message and rewrites its row " , async ( ) => {
482+ test ( "Ctrl+G cancels the last queued message and the screen shows it " , async ( ) => {
483483 await withTestRenderer (
484484 async ( h ) => {
485485 const shell = createAppShell ( h . renderer , {
@@ -494,24 +494,76 @@ describe("product skin: stream + queue + overlay", () => {
494494 submitPrompt ( shell , "queue" )
495495 expect ( shell . pendingQueue ) . toBe ( 2 )
496496
497- const before = shell . streamLog . map ( ( row ) => ( { text : row . text , meta : row . meta } ) )
497+ const before = shell . streamLog . map ( ( row ) => ( {
498+ text : row . text ,
499+ meta : row . meta ,
500+ cancelled : row . cancelled ,
501+ } ) )
498502 expect ( before ) . toEqual ( [
499- { text : "keep this one" , meta : "queue" } ,
500- { text : "oops wrong message" , meta : "queue" } ,
503+ { text : "keep this one" , meta : "queue" , cancelled : undefined } ,
504+ { text : "oops wrong message" , meta : "queue" , cancelled : undefined } ,
501505 ] )
506+ await h . renderOnce ( )
507+ const frameBefore = h . captureCharFrame ( )
508+ expect ( frameBefore ) . toContain ( "keep this one" )
509+ expect ( frameBefore ) . toContain ( "oops wrong message" )
510+ expect ( frameBefore ) . not . toContain ( "[cancelled]" )
502511
503512 applyShellCancelLast ( shell )
504513
505514 expect ( shell . pendingQueue ) . toBe ( 1 )
506515 expect ( shell . session . items [ 0 ] ! . text ) . toBe ( "keep this one" )
507516
508- const after = shell . streamLog . map ( ( row ) => ( { text : row . text , meta : row . meta } ) )
509- // The cancelled row is rewritten, not left claiming "queue" — the
510- // first attempt's bug this test exists to catch.
517+ const after = shell . streamLog . map ( ( row ) => ( {
518+ text : row . text ,
519+ meta : row . meta ,
520+ cancelled : row . cancelled ,
521+ } ) )
522+ // The stored text is untouched — the cancel is a flag the paint
523+ // layer reads, not a rewrite of what the operator typed.
511524 expect ( after ) . toEqual ( [
512- { text : "keep this one" , meta : "queue" } ,
513- { text : "[cancelled] oops wrong message" , meta : "cancelled" } ,
525+ { text : "keep this one" , meta : "queue" , cancelled : undefined } ,
526+ { text : "oops wrong message" , meta : "cancelled" , cancelled : true } ,
514527 ] )
528+
529+ // The screen, not just the model, is asserted on: this is exactly
530+ // what the first attempt at this issue got wrong (the row read
531+ // back unchanged from streamLog while the model looked cancelled).
532+ await h . renderOnce ( )
533+ const frameAfter = h . captureCharFrame ( )
534+ expect ( frameAfter ) . toContain ( "[cancelled] oops wrong message" )
535+ expect ( frameAfter ) . toContain ( "keep this one" )
536+ } finally {
537+ shell . dispose ( )
538+ }
539+ } ,
540+ { width : 80 , height : 24 } ,
541+ )
542+ } )
543+
544+ test ( "Ctrl+G cancels a steered message the same way" , async ( ) => {
545+ await withTestRenderer (
546+ async ( h ) => {
547+ const shell = createAppShell ( h . renderer , {
548+ terminal : { columns : 80 , rows : 24 } ,
549+ wireKeys : false ,
550+ run : "busy" ,
551+ } )
552+ try {
553+ shell . prompt . value = "steer me now"
554+ submitPrompt ( shell , "steer" )
555+ expect ( shell . pendingQueue ) . toBe ( 1 )
556+ expect ( shell . session . items [ 0 ] ! . kind ) . toBe ( "steer" )
557+
558+ applyShellCancelLast ( shell )
559+
560+ expect ( shell . pendingQueue ) . toBe ( 0 )
561+ expect ( shell . session . items ) . toHaveLength ( 0 )
562+ expect ( shell . streamLog [ 0 ] ?. cancelled ) . toBe ( true )
563+ expect ( shell . streamLog [ 0 ] ?. meta ) . toBe ( "cancelled" )
564+
565+ await h . renderOnce ( )
566+ expect ( h . captureCharFrame ( ) ) . toContain ( "[cancelled] steer me now" )
515567 } finally {
516568 shell . dispose ( )
517569 }
0 commit comments