cxstatusline 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "cxstatusline",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Customizable statusline for OpenAI Codex CLI",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/adrijshikhar/cxstatusline.git"
9
9
  },
10
- "homepage": "https://github.com/adrijshikhar/cxstatusline#readme",
10
+ "homepage": "https://cxstatusline.adrijshikhar.dev",
11
11
  "bugs": "https://github.com/adrijshikhar/cxstatusline/issues",
12
12
  "type": "module",
13
13
  "bin": {
@@ -32,6 +32,7 @@
32
32
  "test:website:e2e": "cd website && bun run test:e2e",
33
33
  "seo:drift:baseline": "bun run scripts/seo-drift.ts baseline",
34
34
  "seo:drift:compare": "bun run scripts/seo-drift.ts compare",
35
+ "seo:indexnow": "bun run scripts/indexnow.ts",
35
36
  "check:package": "bun run scripts/check-package.ts",
36
37
  "upstream:watch": "bun run scripts/upstream-watch.ts",
37
38
  "probe:stdio": "bun run scripts/probe-detached-stdio.ts",
@@ -1,3 +1,13 @@
1
+ diff --git a/codex-rs/chatgpt/src/lib.rs b/codex-rs/chatgpt/src/lib.rs
2
+ index 057478d..5589621 100644
3
+ --- a/codex-rs/chatgpt/src/lib.rs
4
+ +++ b/codex-rs/chatgpt/src/lib.rs
5
+ @@ -1,3 +1,5 @@
6
+ +#![recursion_limit = "256"]
7
+ +
8
+ pub mod apply_command;
9
+ mod chatgpt_client;
10
+ pub mod connectors;
1
11
  diff --git a/codex-rs/tui/src/app/background_requests.rs b/codex-rs/tui/src/app/background_requests.rs
2
12
  index 4cdde21..23e1b62 100644
3
13
  --- a/codex-rs/tui/src/app/background_requests.rs
@@ -267,7 +277,7 @@ index 1129bbe..dfd794e 100644
267
277
 
268
278
  #[derive(Debug, Clone, PartialEq, Eq)]
269
279
  diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs
270
- index 42937b1..7d745cd 100644
280
+ index 42937b1..bcbbe5b 100644
271
281
  --- a/codex-rs/tui/src/bottom_pane/chat_composer.rs
272
282
  +++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs
273
283
  @@ -319,6 +319,7 @@ use super::footer::inset_footer_hint_area;
@@ -309,46 +319,93 @@ index 42937b1..7d745cd 100644
309
319
  pub(crate) fn set_side_conversation_context_label(&mut self, label: Option<String>) -> bool {
310
320
  if self.footer.side_conversation_context_label == label {
311
321
  return false;
312
- @@ -4685,6 +4696,26 @@ impl ChatComposer {
313
- } else {
314
- footer_rect
315
- };
316
- + let cxstatusline_height =
317
- + (footer_props.cxstatusline_block.len() as u16).min(hint_rect.height);
318
- + if cxstatusline_height > 0 {
319
- + render_cxstatusline_block(
320
- + Rect::new(
321
- + hint_rect.x,
322
- + hint_rect.y,
323
- + hint_rect.width,
324
- + cxstatusline_height,
325
- + ),
326
- + buf,
327
- + &footer_props.cxstatusline_block,
328
- + );
329
- + }
330
- + let hint_rect = Rect::new(
331
- + hint_rect.x,
332
- + hint_rect.y.saturating_add(cxstatusline_height),
333
- + hint_rect.width,
334
- + hint_rect.height.saturating_sub(cxstatusline_height),
335
- + );
336
- if let Some(input) = self.draft.textarea.vim_query() {
337
- input.render(inset_footer_hint_area(hint_rect), buf);
338
- } else if let Some(line) = self.history_search_footer_line() {
322
+ @@ -4597,6 +4608,7 @@ impl ChatComposer {
323
+ remote_images: remote_images_rect,
324
+ textarea: textarea_rect,
325
+ popup: popup_rect,
326
+ + cxstatusline,
327
+ footer: footer_rect,
328
+ } = self.layout_with_options(area, options);
329
+ self.render_status_surface(status, buf);
330
+ @@ -4640,6 +4652,7 @@ impl ChatComposer {
331
+ }
332
+ }
333
+ }
334
+ + render_cxstatusline_block(cxstatusline, buf, &self.footer.cxstatusline_block);
335
+ let transcript_hint_area = inset_footer_hint_area(footer_rect);
336
+ let warning_notice = self.warning_notice_layout(transcript_hint_area, options);
337
+ let warning_area = warning_notice.as_ref().map(|(area, _)| *area);
338
+ diff --git a/codex-rs/tui/src/bottom_pane/chat_composer/composer_layout.rs b/codex-rs/tui/src/bottom_pane/chat_composer/composer_layout.rs
339
+ index b79c28b..c62c4a6 100644
340
+ --- a/codex-rs/tui/src/bottom_pane/chat_composer/composer_layout.rs
341
+ +++ b/codex-rs/tui/src/bottom_pane/chat_composer/composer_layout.rs
342
+ @@ -102,6 +102,12 @@ impl ChatComposer {
343
+ (composer, popup, popup)
344
+ };
345
+ footer_rect.y = footer_rect.y.saturating_add(status.height);
346
+ + let cxstatusline = Rect {
347
+ + height: (self.footer.cxstatusline_block.len() as u16).min(footer_rect.height),
348
+ + ..footer_rect
349
+ + };
350
+ + footer_rect.y = footer_rect.y.saturating_add(cxstatusline.height);
351
+ + footer_rect.height = footer_rect.height.saturating_sub(cxstatusline.height);
352
+ // Keep the draft visible when clipped.
353
+ let voice_rows = voice_rows * u16::from(composer_rect.height >= voice_rows + 3);
354
+ let mut textarea_rect = composer_rect.inset(Insets::tlbr(
355
+ @@ -133,6 +139,7 @@ impl ChatComposer {
356
+ remote_images: remote_images_rect,
357
+ textarea: textarea_rect,
358
+ popup: popup_rect,
359
+ + cxstatusline,
360
+ footer: footer_rect,
361
+ }
362
+ }
339
363
  diff --git a/codex-rs/tui/src/bottom_pane/chat_composer/footer_state.rs b/codex-rs/tui/src/bottom_pane/chat_composer/footer_state.rs
340
- index f13aaa7..089eb36 100644
364
+ index f13aaa7..2ace588 100644
341
365
  --- a/codex-rs/tui/src/bottom_pane/chat_composer/footer_state.rs
342
366
  +++ b/codex-rs/tui/src/bottom_pane/chat_composer/footer_state.rs
343
- @@ -136,6 +136,7 @@ impl super::ChatComposer {
367
+ @@ -1,6 +1,7 @@
368
+ //! Footer and status-row presentation state for the chat composer.
369
+ //! Owners schedule flash expiry redraws; replacing a draft clears its flash.
370
+ //! One resolved layout supplies the status, input, popup, and hint rectangles to each consumer.
371
+ +//! Renderer-owned status rows reserve their own rectangle above every native footer mode.
372
+ //! Slash-command and unified mention suggestions preserve passive footer content while keeping input.
373
+ //! Interactive transcript footers keep focus over nonempty composer hints. Their owner projects
374
+ //! actual pending chord hints; unrelated hints resume when the transcript interaction closes.
375
+ @@ -39,6 +40,7 @@ pub(super) struct ComposerLayout {
376
+ pub(super) remote_images: Rect,
377
+ pub(super) textarea: Rect,
378
+ pub(super) popup: Rect,
379
+ + pub(super) cxstatusline: Rect,
380
+ pub(super) footer: Rect,
381
+ }
382
+
383
+ @@ -129,16 +131,22 @@ impl super::ChatComposer {
384
+
385
+ pub(super) fn footer_hint_height(&self, width: u16, options: ComposerRenderOptions<'_>) -> u16 {
386
+ if self.show_warning_notice(options) || self.shortcuts_above_composer(options) {
387
+ - return 1;
388
+ + return (self.footer.cxstatusline_block.len() as u16).saturating_add(1);
389
+ }
390
+ options
391
+ .footer
344
392
  .map_or_else(
345
393
  || {
346
394
  self.custom_footer_height()
347
- + .map(|height| height.saturating_add(self.footer.cxstatusline_block.len() as u16))
395
+ + .map(|height| {
396
+ + height.saturating_add(self.footer.cxstatusline_block.len() as u16)
397
+ + })
348
398
  .unwrap_or_else(|| footer_height(&self.hint_footer_props(options), width))
349
399
  },
350
- |footer| footer.text.height().try_into().unwrap_or(u16::MAX),
351
- @@ -213,6 +214,7 @@ pub(super) struct FooterState {
400
+ - |footer| footer.text.height().try_into().unwrap_or(u16::MAX),
401
+ + |footer| {
402
+ + let height: u16 = footer.text.height().try_into().unwrap_or(u16::MAX);
403
+ + height.saturating_add(self.footer.cxstatusline_block.len() as u16)
404
+ + },
405
+ )
406
+ .max(u16::from(options.separate_status_line))
407
+ }
408
+ @@ -213,6 +221,7 @@ pub(super) struct FooterState {
352
409
  pub(super) status_line_value: Option<Line<'static>>,
353
410
  pub(super) status_line_hyperlink_url: Option<String>,
354
411
  pub(super) status_line_enabled: bool,
@@ -356,27 +413,190 @@ index f13aaa7..089eb36 100644
356
413
  pub(super) side_conversation_context_label: Option<String>,
357
414
  pub(super) active_agent_label: Option<String>,
358
415
  pub(super) external_editor_key: Option<ShortcutHint>,
359
- diff --git a/codex-rs/tui/src/bottom_pane/chat_composer/history_search.rs b/codex-rs/tui/src/bottom_pane/chat_composer/history_search.rs
360
- index 9b14b39..884c78d 100644
361
- --- a/codex-rs/tui/src/bottom_pane/chat_composer/history_search.rs
362
- +++ b/codex-rs/tui/src/bottom_pane/chat_composer/history_search.rs
363
- @@ -475,6 +475,16 @@ impl ChatComposer {
364
- /// terminal backends do not receive an off-screen cursor position.
365
- pub(super) fn history_search_cursor_pos(&self, hint_rect: Rect) -> Option<(u16, u16)> {
366
- self.history_search.as_ref()?;
367
- + let hint_rect = Rect::new(
368
- + hint_rect.x,
369
- + hint_rect
370
- + .y
371
- + .saturating_add(self.footer.cxstatusline_block.len() as u16),
372
- + hint_rect.width,
373
- + hint_rect
374
- + .height
375
- + .saturating_sub(self.footer.cxstatusline_block.len() as u16),
376
- + );
377
- if hint_rect.is_empty() {
378
- return None;
416
+ diff --git a/codex-rs/tui/src/bottom_pane/chat_composer/snapshots/codex_tui__bottom_pane__chat_composer__warning_notice__tests__cxstatusline_three_rows_with_warnings.snap b/codex-rs/tui/src/bottom_pane/chat_composer/snapshots/codex_tui__bottom_pane__chat_composer__warning_notice__tests__cxstatusline_three_rows_with_warnings.snap
417
+ new file mode 100644
418
+ index 0000000..64a85de
419
+ --- /dev/null
420
+ +++ b/codex-rs/tui/src/bottom_pane/chat_composer/snapshots/codex_tui__bottom_pane__chat_composer__warning_notice__tests__cxstatusline_three_rows_with_warnings.snap
421
+ @@ -0,0 +1,12 @@
422
+ +---
423
+ +source: tui/src/bottom_pane/chat_composer/warning_notice_tests.rs
424
+ +expression: "text(&render(&composer, 100, 3))"
425
+ +---
426
+ +
427
+ +› Ask Codex
428
+ +
429
+ + MODEL · ~/project
430
+ + CX row one
431
+ + CX row two
432
+ + CX row three
433
+ + ? for shortcuts ⚠ 3 warnings · f2 to view
434
+ diff --git a/codex-rs/tui/src/bottom_pane/chat_composer/warning_notice_tests.rs b/codex-rs/tui/src/bottom_pane/chat_composer/warning_notice_tests.rs
435
+ index 2e11e17..6fe9fab 100644
436
+ --- a/codex-rs/tui/src/bottom_pane/chat_composer/warning_notice_tests.rs
437
+ +++ b/codex-rs/tui/src/bottom_pane/chat_composer/warning_notice_tests.rs
438
+ @@ -163,3 +163,161 @@ fn warnings_yield_to_queue_controls_and_complete_navigation_hints() {
379
439
  }
440
+ }
441
+ }
442
+ +
443
+ +#[test]
444
+ +fn cxstatusline_rows_survive_warning_and_menu_transitions() {
445
+ + let mut composer = composer();
446
+ + composer.set_cxstatusline_block(vec![
447
+ + "CX row one".into(),
448
+ + "CX row two".into(),
449
+ + "CX row three".into(),
450
+ + ]);
451
+ + for draft in ["", "/", "", "hello", ""] {
452
+ + composer.set_text_content(draft.into(), Vec::new(), Vec::new());
453
+ + for count in [0, 3, 0] {
454
+ + let screen = text(&render(&composer, /*width*/ 100, count));
455
+ + for row in ["CX row one", "CX row two", "CX row three"] {
456
+ + assert!(screen.contains(row), "missing {row}: {screen}");
457
+ + }
458
+ + }
459
+ + }
460
+ + insta::assert_snapshot!(
461
+ + "cxstatusline_three_rows_with_warnings",
462
+ + text(&render(&composer, /*width*/ 100, /*count*/ 3))
463
+ + );
464
+ +}
465
+ +
466
+ +#[test]
467
+ +fn cxstatusline_rows_preserve_transcript_cursor_and_shortcut_help() {
468
+ + let mut composer = composer();
469
+ + composer.set_cxstatusline_block(vec![
470
+ + "CX row one".into(),
471
+ + "CX row two".into(),
472
+ + "CX row three".into(),
473
+ + ]);
474
+ + let footer = TranscriptFooter {
475
+ + text: "transcript input".into(),
476
+ + cursor_column: Some(3),
477
+ + is_interactive: true,
478
+ + };
479
+ + for mode in [FooterMode::ComposerEmpty, FooterMode::ShortcutOverlay] {
480
+ + composer.footer.mode = mode;
481
+ + for transcript in [None, Some(&footer)] {
482
+ + let options = composer.resolve_render_options(ComposerRenderOptions {
483
+ + footer: transcript,
484
+ + ..Default::default()
485
+ + });
486
+ + let area = Rect::new(
487
+ + 0,
488
+ + 0,
489
+ + 100,
490
+ + composer.desired_height_with_options(100, options),
491
+ + );
492
+ + let mut buffer = Buffer::empty(area);
493
+ + composer.render_with_options(area, &mut buffer, None, options);
494
+ + let screen = text(&buffer);
495
+ + for row in ["CX row one", "CX row two", "CX row three"] {
496
+ + assert!(screen.contains(row), "missing {row}: {screen}");
497
+ + }
498
+ + if transcript.is_some() {
499
+ + let row = screen
500
+ + .lines()
501
+ + .position(|line| line.contains("transcript input"))
502
+ + .unwrap();
503
+ + assert_eq!(
504
+ + composer.cursor_pos_with_options(area, options),
505
+ + Some((5, row as u16))
506
+ + );
507
+ + }
508
+ + }
509
+ + }
510
+ +}
511
+ +
512
+ +#[test]
513
+ +fn cxstatusline_rows_survive_real_slash_popup_input_and_resize() {
514
+ + let mut composer = composer();
515
+ + composer.set_collaboration_modes_enabled(true);
516
+ + composer.set_cxstatusline_block(vec![
517
+ + "CX row one".into(),
518
+ + "CX row two".into(),
519
+ + "CX row three".into(),
520
+ + ]);
521
+ + for ch in ['/', 'm'] {
522
+ + composer.handle_key_event(KeyEvent::from(KeyCode::Char(ch)));
523
+ + }
524
+ + composer.handle_key_event(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE));
525
+ + assert!(matches!(composer.popups.active, ActivePopup::Command(_)));
526
+ +
527
+ + for width in [80, 32] {
528
+ + let options = composer.resolve_render_options(ComposerRenderOptions {
529
+ + separate_status_line: true,
530
+ + command_popup_placement: CommandPopupPlacement::AboveComposer,
531
+ + ..Default::default()
532
+ + });
533
+ + let area = Rect::new(
534
+ + 0,
535
+ + 0,
536
+ + width,
537
+ + composer.desired_height_with_options(width, options),
538
+ + );
539
+ + let mut buffer = Buffer::empty(area);
540
+ + composer.render_with_options(area, &mut buffer, None, options);
541
+ + let layout = composer.layout_with_options(area, options);
542
+ + assert!(
543
+ + layout.popup.height > 0,
544
+ + "slash popup should remain open at width {width}: {}",
545
+ + text(&buffer)
546
+ + );
547
+ + assert_eq!(layout.cxstatusline.height, 3);
548
+ + assert_eq!(layout.cxstatusline.bottom(), layout.footer.y);
549
+ + for row in ["CX row one", "CX row two", "CX row three"] {
550
+ + assert!(
551
+ + text(&buffer).contains(row),
552
+ + "missing {row} at width {width}"
553
+ + );
554
+ + }
555
+ + let (x, y) = composer
556
+ + .cursor_pos_with_options(area, options)
557
+ + .expect("input cursor");
558
+ + assert!(
559
+ + x >= area.x && x < area.right(),
560
+ + "cursor x outside width {width}: {x}"
561
+ + );
562
+ + assert!(
563
+ + y >= area.y && y < area.bottom(),
564
+ + "cursor y outside screen: {y}"
565
+ + );
566
+ + }
567
+ +
568
+ + composer.handle_key_event(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
569
+ + assert!(matches!(composer.popups.active, ActivePopup::None));
570
+ + let options = composer.resolve_render_options(ComposerRenderOptions {
571
+ + separate_status_line: true,
572
+ + command_popup_placement: CommandPopupPlacement::AboveComposer,
573
+ + ..Default::default()
574
+ + });
575
+ + let area = Rect::new(0, 0, 80, composer.desired_height_with_options(80, options));
576
+ + let layout = composer.layout_with_options(area, options);
577
+ + assert_eq!(layout.cxstatusline.height, 3);
578
+ +
579
+ + for running in [true, false] {
580
+ + composer.set_task_running(running);
581
+ + let buffer = render(&composer, 80, 0);
582
+ + for row in ["CX row one", "CX row two", "CX row three"] {
583
+ + assert!(
584
+ + text(&buffer).contains(row),
585
+ + "missing {row}, running={running}"
586
+ + );
587
+ + }
588
+ + let layout = composer.layout_with_options(buffer.area, options);
589
+ + let (_, y) = composer
590
+ + .cursor_pos_with_options(buffer.area, options)
591
+ + .expect("input cursor");
592
+ + assert!(
593
+ + y < layout.cxstatusline.y,
594
+ + "cursor overlaps footer, running={running}"
595
+ + );
596
+ + }
597
+ + composer.handle_key_event(KeyEvent::from(KeyCode::Char('x')));
598
+ + assert!(text(&render(&composer, 80, 0)).contains("/mx"));
599
+ +}
380
600
  diff --git a/codex-rs/tui/src/bottom_pane/footer.rs b/codex-rs/tui/src/bottom_pane/footer.rs
381
601
  index 76a13b7..8479e1e 100644
382
602
  --- a/codex-rs/tui/src/bottom_pane/footer.rs
@@ -1,3 +1,107 @@
1
+ diff --git a/build/ink.d.ts b/build/ink.d.ts
2
+ index 9dce0fe328a8eeb18d459b974ab0c3cf5fb38dda..f90bb0fe8c71beae7bf7db4e4d9d5f62812d33a3 100644
3
+ --- a/build/ink.d.ts
4
+ +++ b/build/ink.d.ts
5
+ @@ -4,6 +4,8 @@ export type Options = {
6
+ stdin: NodeJS.ReadStream;
7
+ stderr: NodeJS.WriteStream;
8
+ debug: boolean;
9
+ + preserveScrollback?: boolean;
10
+ + isCI?: boolean;
11
+ exitOnCtrlC: boolean;
12
+ patchConsole: boolean;
13
+ isScreenReaderEnabled?: boolean;
14
+ @@ -14,6 +16,7 @@ export default class Ink {
15
+ private readonly log;
16
+ private readonly throttledLog;
17
+ private readonly isScreenReaderEnabled;
18
+ + private readonly isInCi;
19
+ private isUnmounted;
20
+ private lastOutput;
21
+ private lastOutputHeight;
22
+ diff --git a/build/ink.js b/build/ink.js
23
+ index 5cad56e135f1074474d7d498ffd87c5699b9df78..46acd25d0df2d4382f2bb5c2586f4d802bbb4af2 100644
24
+ --- a/build/ink.js
25
+ +++ b/build/ink.js
26
+ @@ -21,6 +21,7 @@ export default class Ink {
27
+ log;
28
+ throttledLog;
29
+ isScreenReaderEnabled;
30
+ + isInCi;
31
+ // Ignore last render after unmounting a tree to prevent empty output before exit
32
+ isUnmounted;
33
+ lastOutput;
34
+ @@ -36,6 +37,7 @@ export default class Ink {
35
+ constructor(options) {
36
+ autoBind(this);
37
+ this.options = options;
38
+ + this.isInCi = options.isCI ?? isInCi;
39
+ this.rootNode = dom.createNode('ink-root');
40
+ this.rootNode.onComputeLayout = this.calculateLayout;
41
+ this.isScreenReaderEnabled =
42
+ @@ -83,7 +85,7 @@ export default class Ink {
43
+ if (options.patchConsole) {
44
+ this.patchConsole();
45
+ }
46
+ - if (!isInCi) {
47
+ + if (!this.isInCi) {
48
+ options.stdout.on('resize', this.resized);
49
+ this.unsubscribeResize = () => {
50
+ options.stdout.off('resize', this.resized);
51
+ @@ -118,7 +120,7 @@ export default class Ink {
52
+ this.options.stdout.write(this.fullStaticOutput + output);
53
+ return;
54
+ }
55
+ - if (isInCi) {
56
+ + if (this.isInCi) {
57
+ if (hasStaticOutput) {
58
+ this.options.stdout.write(staticOutput);
59
+ }
60
+ @@ -129,7 +131,7 @@ export default class Ink {
61
+ if (hasStaticOutput) {
62
+ this.fullStaticOutput += staticOutput;
63
+ }
64
+ - if (this.lastOutputHeight >= this.options.stdout.rows) {
65
+ + if (!this.options.preserveScrollback && this.lastOutputHeight >= this.options.stdout.rows) {
66
+ this.options.stdout.write(ansiEscapes.clearTerminal + this.fullStaticOutput + output + '\n');
67
+ this.lastOutput = output;
68
+ this.lastOutputHeight = outputHeight;
69
+ @@ -166,7 +168,7 @@ export default class Ink {
70
+ this.options.stdout.write(data + this.fullStaticOutput + this.lastOutput);
71
+ return;
72
+ }
73
+ - if (isInCi) {
74
+ + if (this.isInCi) {
75
+ this.options.stdout.write(data);
76
+ return;
77
+ }
78
+ @@ -183,7 +185,7 @@ export default class Ink {
79
+ this.options.stdout.write(this.fullStaticOutput + this.lastOutput);
80
+ return;
81
+ }
82
+ - if (isInCi) {
83
+ + if (this.isInCi) {
84
+ this.options.stderr.write(data);
85
+ return;
86
+ }
87
+ @@ -207,7 +209,7 @@ export default class Ink {
88
+ }
89
+ // CIs don't handle erasing ansi escapes well, so it's better to
90
+ // only render last frame of non-static output
91
+ - if (isInCi) {
92
+ + if (this.isInCi) {
93
+ this.options.stdout.write(this.lastOutput + '\n');
94
+ }
95
+ else if (!this.options.debug) {
96
+ @@ -236,7 +238,7 @@ export default class Ink {
97
+ return this.exitPromise;
98
+ }
99
+ clear() {
100
+ - if (!isInCi && !this.options.debug) {
101
+ + if (!this.isInCi && !this.options.debug) {
102
+ this.log.clear();
103
+ }
104
+ }
1
105
  diff --git a/build/parse-keypress.js b/build/parse-keypress.js
2
106
  index 1e13e819f59d259a3476510db822695576a9d93c..e100611d8661187c4ca191e89621cd5e480fcacc 100644
3
107
  --- a/build/parse-keypress.js
@@ -15,3 +119,18 @@ index 1e13e819f59d259a3476510db822695576a9d93c..e100611d8661187c4ca191e89621cd5e
15
119
  key.meta = s.charAt(0) === '\x1b';
16
120
  }
17
121
  else if (s === '\x1b' || s === '\x1b\x1b') {
122
+ diff --git a/build/render.d.ts b/build/render.d.ts
123
+ index ffcfbaca277cbd95b2e6a2c857230dd69e41b7e0..5fd94d71aa136f48447c162dd7453617f1824cc5 100644
124
+ --- a/build/render.d.ts
125
+ +++ b/build/render.d.ts
126
+ @@ -24,6 +24,10 @@ export type RenderOptions = {
127
+ * @default false
128
+ */
129
+ debug?: boolean;
130
+ + /** Preserve prior terminal output when an Ink view exceeds terminal height. */
131
+ + preserveScrollback?: boolean;
132
+ + /** Override CI detection when the caller has verified an interactive terminal. */
133
+ + isCI?: boolean;
134
+ /**
135
+ * Configure whether Ink should listen to Ctrl+C keyboard input and exit the app. This is needed in case `process.stdin` is in raw mode, because then Ctrl+C is ignored by default and process is expected to handle it manually.
136
+ *
@@ -1,14 +1,16 @@
1
1
  {
2
- "version": 1,
2
+ "version": 2,
3
3
  "tag_prefix": "rust-v",
4
4
  "candidate": "0.156.1",
5
5
  "patches": [
6
- { "min": "0.152.1", "max": "0.152.1", "file": "codex-0.152.1.patch" },
7
- { "min": "0.153.0", "max": "0.153.0", "file": "codex-0.153.0.patch" },
8
- { "min": "0.153.4", "max": "0.153.4", "file": "codex-0.153.4.patch" },
9
- { "min": "0.154.0", "max": "0.154.0", "file": "codex-0.154.0.patch" },
10
- { "min": "0.155.0", "max": "0.155.0", "file": "codex-0.155.0.patch" },
11
- { "min": "0.155.1", "max": "0.155.1", "file": "codex-0.155.1.patch" },
12
- { "min": "0.156.1", "max": "0.156.1", "file": "codex-0.156.1.patch" }
6
+ { "patchVersion": 1, "min": "0.152.1", "max": "0.152.1", "file": "codex-0.152.1.patch" },
7
+ { "patchVersion": 1, "min": "0.153.0", "max": "0.153.0", "file": "codex-0.153.0.patch" },
8
+ { "patchVersion": 1, "min": "0.153.1", "max": "0.153.1", "file": "codex-0.153.4.patch" },
9
+ { "patchVersion": 1, "min": "0.153.4", "max": "0.153.4", "file": "codex-0.153.4.patch" },
10
+ { "patchVersion": 1, "min": "0.154.0", "max": "0.154.0", "file": "codex-0.154.0.patch" },
11
+ { "patchVersion": 1, "min": "0.155.0", "max": "0.155.0", "file": "codex-0.155.0.patch" },
12
+ { "patchVersion": 1, "min": "0.155.1", "max": "0.155.1", "file": "codex-0.155.1.patch" },
13
+ { "patchVersion": 2, "min": "0.156.0", "max": "0.156.0", "file": "codex-0.156.1.patch" },
14
+ { "patchVersion": 2, "min": "0.156.1", "max": "0.156.1", "file": "codex-0.156.1.patch" }
13
15
  ]
14
16
  }