@@ -441,11 +441,39 @@ export function applyPendingNav(shell: AppShell, delta: -1 | 1): boolean {
441441 return true ;
442442}
443443
444+ /**
445+ * No-runtime path for leaving the queue through the selected row: kill the
446+ * selected item out of the queue. Same contract as applyShellCancelLast —
447+ * an empty prompt gets the item back for editing; mid-draft it's dropped
448+ * rather than merged, since merging would send two messages as one.
449+ */
450+ function popSelectedToPrompt ( shell : AppShell ) : void {
451+ const bag = shellInternals ( shell ) ;
452+ const sel = pendingSelIndex ( shell ) ;
453+ if ( bag === undefined || sel < 0 ) return ;
454+ const id = shell . session . items [ sel ] ?. id ;
455+ if ( id === undefined ) return ;
456+ const { state, item : popped } = cancelItem ( shell . session , id ) ;
457+ if ( popped === null ) return ;
458+ shell . session = state ;
459+ bag . pendingSelId = null ;
460+ if ( shell . prompt . value . length === 0 ) {
461+ shell . prompt . value = popped . text ;
462+ shell . prompt . cursorOffset = popped . text . length ;
463+ shell . pendingAttachments = [
464+ ...shell . pendingAttachments ,
465+ ...( popped . attachments ?? [ ] ) ,
466+ ] ;
467+ }
468+ paintChrome ( shell ) ;
469+ }
470+
444471/**
445472 * Enter on a selected pending item: kill it out of the queue and force-push —
446473 * deliver it now through the runtime, skipping its boundary/idle wait. With
447- * no runtime attached there is nothing to deliver to, so it pops back into
448- * the prompt for editing instead.
474+ * no runtime attached there is nothing to deliver to, so it falls back to
475+ * the cancel contract: back into an empty prompt for editing, dropped
476+ * mid-draft rather than merged.
449477 */
450478export function applyPendingForcePush ( shell : AppShell ) : void {
451479 const bag = shellInternals ( shell ) ;
@@ -461,21 +489,17 @@ export function applyPendingForcePush(shell: AppShell): void {
461489 paintChrome ( shell ) ;
462490 return ;
463491 }
464- const { state, item : popped } = cancelItem ( shell . session , item . id ) ;
465- if ( popped === null ) return ;
466- shell . session = state ;
467- bag . pendingSelId = null ;
468- // Same contract as applyShellCancelLast: an empty prompt gets the item
469- // back for editing; mid-draft it's dropped rather than merged.
470- if ( shell . prompt . value . length === 0 ) {
471- shell . prompt . value = popped . text ;
472- shell . prompt . cursorOffset = popped . text . length ;
473- shell . pendingAttachments = [
474- ...shell . pendingAttachments ,
475- ...( popped . attachments ?? [ ] ) ,
476- ] ;
477- }
478- paintChrome ( shell ) ;
492+ popSelectedToPrompt ( shell ) ;
493+ }
494+
495+ /**
496+ * Ctrl+G on a selected pending item: cancel that row, not the newest — the
497+ * operator pointed at it. Without hooks this is the same pop-to-prompt as
498+ * the force-push fallback; with a runtime attached it still only cancels,
499+ * never delivers.
500+ */
501+ export function applyPendingCancelSelected ( shell : AppShell ) : void {
502+ popSelectedToPrompt ( shell ) ;
479503}
480504
481505/**
0 commit comments