@oxecli/oxe 1.0.92 → 1.0.94

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.
Files changed (2) hide show
  1. package/dist/ui.js +73 -36
  2. package/package.json +1 -1
package/dist/ui.js CHANGED
@@ -603,21 +603,39 @@ export function dockTearDown() {
603
603
  dockFrameLines = [];
604
604
  }
605
605
  }
606
- /** Park a transient spinner two rows above the box (dockRel=2), keeping boxTop-1
607
- * as the single blank gap. "flush" parks the spinner directly under the
608
- * preceding content (label, spinner, blank, box); "blank" gives it its own
609
- * blank row above (echo, blank, spinner, blank, box). Idempotent while parked. */
606
+ /** Park a transient spinner row above the box. "flush" parks it directly under
607
+ * the preceding content (so a tool result is adjacent to its label); "blank"
608
+ * gives it its own blank row above. The single blank gap between content and
609
+ * box is always preserved. Idempotent while parked.
610
+ *
611
+ * This dock uses an erase-and-redraw model (never insert-lines), so it stays
612
+ * stable when the box sits near the bottom of a real terminal, where
613
+ * inserting lines would otherwise scroll the box off-screen. */
610
614
  export function dockTransientStart(mode = "blank") {
611
615
  if (!docked || dockBoxRows < 1)
612
616
  return;
613
617
  if (dockTransient)
614
618
  return;
615
- const insert = mode === "flush" ? 1 : 2;
616
- process.stdout.write(`\x1b[${insert}L`);
617
- if (mode === "flush")
618
- process.stdout.write("\x1b[1A");
619
- process.stdout.write("\r");
620
- dockRel = 2;
619
+ dockEraseBox();
620
+ if (mode === "flush") {
621
+ // Park on the gap row directly under the preceding content (adjacent).
622
+ process.stdout.write("\x1b[1A\r");
623
+ process.stdout.write("\n");
624
+ process.stdout.write(dockFrameLines.join("\n"));
625
+ process.stdout.write(`\x1b[${dockBoxRows - 1}A`);
626
+ process.stdout.write("\x1b[1A\r");
627
+ dockRel = 1;
628
+ }
629
+ else {
630
+ // Park on a fresh spinner row that keeps a blank above and below it.
631
+ process.stdout.write("\n\n");
632
+ process.stdout.write(dockFrameLines.join("\n"));
633
+ process.stdout.write(`\x1b[${dockBoxRows - 1}A`);
634
+ process.stdout.write("\x1b[2A\r");
635
+ dockRel = 2;
636
+ }
637
+ docked = true;
638
+ dockGap = false;
621
639
  dockTransient = true;
622
640
  }
623
641
  /** Erase the box and any transient rows above it, leaving the cursor at boxTop
@@ -642,9 +660,37 @@ function dockEraseBox() {
642
660
  dockGap = true;
643
661
  dockTransient = false;
644
662
  }
645
- /** Commit content at the box top: insert `rows+1` lines to push the box down
646
- * (no erase/redraw), write the body, leave exactly one blank row between the
647
- * content and the box, and park the cursor at the new boxTop. */
663
+ /** Erase the box and transient rows, leaving the cursor on the transient row
664
+ * (so dockReplaceTransient can write new content in its place). */
665
+ function dockEraseBoxKeep() {
666
+ if (!docked || dockBoxRows < 1)
667
+ return;
668
+ process.stdout.write(`\x1b[${dockBoxRows - 1 + dockRel}B`);
669
+ for (let i = 0; i < dockBoxRows; i++) {
670
+ process.stdout.write("\r\x1b[K");
671
+ if (i < dockBoxRows - 1)
672
+ process.stdout.write("\x1b[1A");
673
+ }
674
+ process.stdout.write(`\x1b[${dockRel}A`);
675
+ docked = false;
676
+ dockRel = 0;
677
+ dockGap = true;
678
+ dockTransient = false;
679
+ }
680
+ /** Write one blank gap row then the box frame below the current row, and park
681
+ * the cursor at the box top. */
682
+ function dockRenderBox() {
683
+ if (dockBoxRows < 1)
684
+ return;
685
+ process.stdout.write("\n\n" + dockFrameLines.join("\n"));
686
+ process.stdout.write(`\x1b[${dockBoxRows - 1}A\r`);
687
+ dockRel = 0;
688
+ docked = true;
689
+ dockGap = false;
690
+ dockTransient = false;
691
+ }
692
+ /** Commit content at the box top, pushing the box down, preserving exactly one
693
+ * blank row between the content and the box. */
648
694
  export function dockAppendContent(text) {
649
695
  if (!text)
650
696
  return;
@@ -652,18 +698,15 @@ export function dockAppendContent(text) {
652
698
  process.stdout.write(text);
653
699
  return;
654
700
  }
655
- const body = text.replace(/\n+$/, "");
656
- if (!body)
657
- return;
658
- const rows = displayRows(body);
659
- process.stdout.write(`\x1b[${rows + 1}L`);
660
- process.stdout.write(body + "\n\r");
661
- process.stdout.write("\x1b[1B\r");
701
+ dockEraseBox();
702
+ process.stdout.write(text);
703
+ const tb = displayRows(text) - displayRows(text.replace(/\n+$/, ""));
704
+ if (tb > 0)
705
+ process.stdout.write(`\x1b[${tb}A`);
706
+ dockRenderBox();
662
707
  }
663
- /** Replace a transient spinner row (dockRel=2, two rows above the box) with
664
- * real (possibly multi-row) content: erase the spinner line, push the box
665
- * down by `rows-1` if the content is taller than the transient row, write the
666
- * content, and park the cursor at the boxTop with one blank row above it. */
708
+ /** Replace a transient spinner row with real (possibly multi-row) content, then
709
+ * re-dock, keeping exactly one blank row between content and box. */
667
710
  export function dockReplaceTransient(text) {
668
711
  if (!text)
669
712
  return;
@@ -675,18 +718,12 @@ export function dockReplaceTransient(text) {
675
718
  dockAppendContent(text);
676
719
  return;
677
720
  }
678
- const body = text.replace(/\n+$/, "");
679
- if (!body)
680
- return;
681
- const rows = displayRows(body);
682
- const push = rows + 1 - dockRel;
683
- process.stdout.write("\r\x1b[K");
684
- if (push > 0)
685
- process.stdout.write(`\x1b[${push}L`);
686
- process.stdout.write(body);
687
- process.stdout.write("\x1b[2B\r");
688
- dockRel = 0;
689
- dockTransient = false;
721
+ dockEraseBoxKeep();
722
+ process.stdout.write(text);
723
+ const tb = displayRows(text) - displayRows(text.replace(/\n+$/, ""));
724
+ if (tb > 0)
725
+ process.stdout.write(`\x1b[${tb}A`);
726
+ dockRenderBox();
690
727
  }
691
728
  // ---------------------------------------------------------------------------
692
729
  // Cursor & Spinner
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxecli/oxe",
3
- "version": "1.0.92",
3
+ "version": "1.0.94",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },