@tt-a1i/openpi 0.4.0 → 0.6.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.
Files changed (141) hide show
  1. package/README.md +116 -46
  2. package/SETUP.md +29 -7
  3. package/THIRD_PARTY_NOTICES.md +16 -0
  4. package/assets/openpi-launch-card-v1.webp +0 -0
  5. package/bin/openpi.js +155 -0
  6. package/extensions/ai-providers/LICENSE.upstream +23 -0
  7. package/extensions/ai-providers/README.md +59 -0
  8. package/extensions/ai-providers/antigravity/credentials.ts +52 -0
  9. package/extensions/ai-providers/antigravity/discovery.ts +130 -0
  10. package/extensions/ai-providers/antigravity/google-conversion.ts +455 -0
  11. package/extensions/ai-providers/antigravity/models.ts +84 -0
  12. package/extensions/ai-providers/antigravity/oauth.ts +700 -0
  13. package/extensions/ai-providers/antigravity/provider.ts +1116 -0
  14. package/extensions/ai-providers/antigravity/routing.ts +340 -0
  15. package/extensions/ai-providers/antigravity/with-resolvers.d.ts +19 -0
  16. package/extensions/ai-providers/cursor/constants.ts +5 -0
  17. package/extensions/ai-providers/cursor/credentials.ts +14 -0
  18. package/extensions/ai-providers/cursor/discovery.ts +291 -0
  19. package/extensions/ai-providers/cursor/input-images.ts +106 -0
  20. package/extensions/ai-providers/cursor/models.ts +45 -0
  21. package/extensions/ai-providers/cursor/oauth.ts +263 -0
  22. package/extensions/ai-providers/cursor/proto.ts +1064 -0
  23. package/extensions/ai-providers/cursor/protobuf.ts +1171 -0
  24. package/extensions/ai-providers/cursor/provider.ts +1175 -0
  25. package/extensions/ai-providers/cursor/proxy.ts +213 -0
  26. package/extensions/ai-providers/cursor/with-resolvers.d.ts +12 -0
  27. package/extensions/ai-providers/index.ts +86 -0
  28. package/extensions/ai-providers/oauth-adapter.ts +81 -0
  29. package/extensions/ai-providers/usage.ts +10 -0
  30. package/extensions/background-terminals/index.ts +38 -3
  31. package/extensions/background-terminals/src/domain.ts +2 -0
  32. package/extensions/background-terminals/src/manager.ts +484 -106
  33. package/extensions/background-terminals/src/output.ts +33 -0
  34. package/extensions/background-terminals/src/prompt.ts +13 -5
  35. package/extensions/background-terminals/src/result-delivery.ts +47 -24
  36. package/extensions/clear-context/index.ts +83 -0
  37. package/extensions/context-pivot/index.ts +16 -6
  38. package/extensions/cron/index.ts +68 -27
  39. package/extensions/cron/schedule.ts +12 -2
  40. package/extensions/file-mutation-display/render.ts +17 -257
  41. package/extensions/file-search/src/binaries.ts +57 -41
  42. package/extensions/git-read/index.ts +1 -3
  43. package/extensions/model-info/cache-diagnostics.ts +220 -0
  44. package/extensions/model-info/index.ts +65 -33
  45. package/extensions/model-info/session-metrics.ts +96 -0
  46. package/extensions/plan-mode/bash-policy.ts +54 -9
  47. package/extensions/plan-mode/index.ts +82 -6
  48. package/extensions/post-edit/index.ts +16 -6
  49. package/extensions/sessions/git-stats.ts +258 -72
  50. package/extensions/sessions/index.ts +153 -86
  51. package/extensions/sessions/preview-cache.ts +104 -0
  52. package/extensions/sessions/preview-loader.ts +856 -0
  53. package/extensions/sessions/sessions.ts +43 -4
  54. package/extensions/setup/index.ts +138 -130
  55. package/extensions/shared/activity-status.ts +30 -0
  56. package/extensions/shared/agent-session-page.ts +319 -0
  57. package/extensions/shared/agent-tool-renderer.ts +218 -0
  58. package/extensions/shared/agent-transcript.ts +524 -0
  59. package/extensions/shared/capability-intent.ts +1 -1
  60. package/extensions/shared/child-session.ts +457 -21
  61. package/extensions/shared/completion-inbox.ts +193 -0
  62. package/extensions/shared/result-delivery.ts +34 -0
  63. package/extensions/shared/setup-config.ts +83 -34
  64. package/extensions/shared/setup-episode-state.ts +1 -1
  65. package/extensions/shared/structured-output.ts +154 -0
  66. package/extensions/shared/terminal-text.ts +110 -23
  67. package/extensions/shared/text-projection.ts +72 -15
  68. package/extensions/shared/tool-activity.ts +382 -0
  69. package/extensions/shared/tool-surface.ts +29 -2
  70. package/extensions/shared/transcript-viewport.ts +46 -0
  71. package/extensions/shared/web-observer-registry.ts +390 -0
  72. package/extensions/shared/worktree.ts +11 -0
  73. package/extensions/subagents/index.ts +313 -62
  74. package/extensions/subagents/navigation.ts +34 -5
  75. package/extensions/subagents/src/backend.ts +12 -1
  76. package/extensions/subagents/src/backends/pi.ts +450 -70
  77. package/extensions/subagents/src/domain.ts +21 -1
  78. package/extensions/subagents/src/manager.ts +39 -2
  79. package/extensions/subagents/src/prompt.ts +49 -7
  80. package/extensions/subagents/src/result-artifact.ts +36 -0
  81. package/extensions/subagents/src/result-delivery.ts +39 -14
  82. package/extensions/subagents/src/runtime.ts +15 -1
  83. package/extensions/subagents/src/ui/takeover.ts +73 -257
  84. package/extensions/subagents/src/ui/transcript.ts +38 -535
  85. package/extensions/subagents/src/ui/wait-result.ts +103 -15
  86. package/extensions/suggestions/src/ui.ts +10 -4
  87. package/extensions/tasks/index.ts +0 -3
  88. package/extensions/ui-customization/footer.ts +16 -45
  89. package/extensions/ui-customization/index.ts +0 -4
  90. package/extensions/user-input-fold/index.ts +42 -6
  91. package/extensions/web/index.ts +257 -0
  92. package/extensions/workflows/acceptance.ts +43 -19
  93. package/extensions/workflows/artifacts.ts +137 -47
  94. package/extensions/workflows/completion-projection.ts +459 -0
  95. package/extensions/workflows/coordinator.ts +8 -10
  96. package/extensions/workflows/dashboard.ts +175 -228
  97. package/extensions/workflows/handoff.ts +70 -16
  98. package/extensions/workflows/index.ts +501 -198
  99. package/extensions/workflows/journal.ts +148 -13
  100. package/extensions/workflows/model.ts +79 -5
  101. package/extensions/workflows/navigation.ts +32 -8
  102. package/extensions/workflows/progress-projection.ts +306 -0
  103. package/extensions/workflows/prompt.ts +70 -16
  104. package/extensions/workflows/replay-safety.ts +42 -21
  105. package/extensions/workflows/result-delivery.ts +214 -76
  106. package/extensions/workflows/retention.ts +599 -0
  107. package/extensions/workflows/runner.ts +389 -345
  108. package/extensions/workflows/sandbox-child.cjs +25 -3
  109. package/extensions/workflows/sandbox.ts +62 -8
  110. package/extensions/workflows/serialization.ts +325 -17
  111. package/extensions/workflows/tool-renderer.ts +22 -0
  112. package/extensions/workflows/transcript.ts +149 -0
  113. package/extensions/workspace-cleanup-guard/index.ts +54 -0
  114. package/extensions/workspace-cleanup-guard/workspace-provenance.ts +563 -0
  115. package/package.json +34 -14
  116. package/skills/subagents/REFERENCE.md +190 -0
  117. package/skills/subagents/SKILL.md +2 -1
  118. package/skills/workflows/REFERENCE.md +6 -4
  119. package/skills/workflows/SKILL.md +1 -1
  120. package/web/adapter/pi-adapter.ts +664 -0
  121. package/web/host/browser-launcher.ts +20 -0
  122. package/web/host/pi-coding-agent-entry.ts +162 -0
  123. package/web/host/static-assets.ts +4 -0
  124. package/web/host/terminal-status.ts +38 -0
  125. package/web/host/web-host.ts +1069 -0
  126. package/web/http-dispatcher.ts +125 -0
  127. package/web/protocol/types.ts +467 -0
  128. package/web/runtime/pi-runtime.ts +1206 -0
  129. package/web/runtime/types.ts +102 -0
  130. package/web/runtime/web-host-lease.ts +497 -0
  131. package/web/trace.ts +18 -0
  132. package/web/ui/app.js +1700 -0
  133. package/web/ui/index.html +142 -0
  134. package/web/ui/styles.css +680 -0
  135. package/web/vite.config.mjs +34 -0
  136. package/extensions/execution-convergence/active-evidence.ts +0 -129
  137. package/extensions/execution-convergence/index.ts +0 -442
  138. package/extensions/execution-convergence/workspace-provenance.ts +0 -338
  139. package/extensions/setup/intercom-fs-helper.cjs +0 -130
  140. package/extensions/setup/intercom.ts +0 -603
  141. package/extensions/subagents/src/backends/stub.ts +0 -303
@@ -2,8 +2,8 @@
2
2
  * Takeover UI for subagents (ported from v1, rendering from the synchronous
3
3
  * SubagentReadModel instead of live pi sessions):
4
4
  * - SubagentDashboard: compact picker docked above the input, listing all subagents.
5
- * - TakeoverView: full interactive view of one subagent with an input line
6
- * to steer/continue it.
5
+ * - TakeoverView: full read-only view of one subagent; steering remains owned
6
+ * by the parent model through the subagent tools.
7
7
  */
8
8
 
9
9
  import type {
@@ -12,22 +12,15 @@ import type {
12
12
  Theme,
13
13
  } from "@earendil-works/pi-coding-agent";
14
14
  import type { Component, Focusable, TUI } from "@earendil-works/pi-tui";
15
- import { Input, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
16
- import {
17
- hintLine,
18
- panelFrame,
19
- type ScreenHint,
20
- } from "../../../shared/screen-chrome.ts";
15
+ import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
16
+ import { AgentSessionPage } from "../../../shared/agent-session-page.ts";
17
+ import { hintLine, panelFrame } from "../../../shared/screen-chrome.ts";
21
18
  import { sanitizeTerminalText } from "../../../shared/terminal-text.ts";
22
19
  import { formatElapsed, type SubagentSnapshot } from "../domain.ts";
23
20
  import { formatContextUtilization } from "../../../shared/context-utilization.ts";
21
+ import { SPINNER_INTERVAL_MS, spinnerFrame } from "../../../shared/spinner.ts";
24
22
  import type { SubagentReadModel } from "../manager.ts";
25
- import {
26
- buildTranscriptLines,
27
- SPINNER_INTERVAL_MS,
28
- spinnerFrame,
29
- TranscriptRenderer,
30
- } from "./transcript.ts";
23
+ import { subagentTranscriptDocument } from "./transcript.ts";
31
24
 
32
25
  export function sanitizeSubagentDisplayLine(value: string) {
33
26
  return sanitizeTerminalText(value).replace(/\s+/g, " ").trim();
@@ -43,7 +36,7 @@ function configuredKeys(
43
36
  /**
44
37
  * One spinner definition for the whole subagent UI: the dashboard glyph, the
45
38
  * takeover header, and the transcript's live tools must animate in step, so the
46
- * frames and their cadence live in `transcript.ts` and are imported here.
39
+ * frames and their cadence live in `shared/spinner.ts` and are imported here.
47
40
  */
48
41
  function statusGlyph(
49
42
  snap: SubagentSnapshot,
@@ -69,6 +62,8 @@ function statusGlyph(
69
62
 
70
63
  export interface TakeoverOptions {
71
64
  readonly badge?: string;
65
+ /** Captured from the parent UI when this child page opens. */
66
+ readonly toolsExpanded?: boolean;
72
67
  }
73
68
 
74
69
  export async function openSubagentTakeover(
@@ -78,12 +73,24 @@ export async function openSubagentTakeover(
78
73
  options?: TakeoverOptions,
79
74
  ) {
80
75
  if (!view.get(id)) return;
76
+ const takeoverOptions: TakeoverOptions = {
77
+ ...options,
78
+ toolsExpanded: ctx.ui.getToolsExpanded(),
79
+ };
81
80
  await ctx.ui.custom<null>(
82
81
  (tui, theme, keybindings, done) =>
83
- new TakeoverView(tui, theme, keybindings, id, view, done, options),
82
+ new TakeoverView(
83
+ tui,
84
+ theme,
85
+ keybindings,
86
+ id,
87
+ view,
88
+ done,
89
+ takeoverOptions,
90
+ ),
84
91
  {
85
92
  overlay: true,
86
- overlayOptions: { anchor: "center", width: "100%", maxHeight: "100%" },
93
+ overlayOptions: { anchor: "top-left", width: "100%", maxHeight: "100%" },
87
94
  },
88
95
  );
89
96
  }
@@ -387,33 +394,18 @@ export class SubagentDashboard implements Component {
387
394
 
388
395
  // --- Takeover view ------------------------------------------------------------
389
396
 
390
- const TRANSCRIPT_SCROLL_STEP = 6;
391
-
392
397
  export class TakeoverView implements Component, Focusable {
393
- private tui: TUI;
394
- private theme: Theme;
395
- private keybindings: KeybindingsManager;
396
- private id: string;
397
- private view: SubagentReadModel;
398
- private done: (value: null) => void;
399
- private options?: TakeoverOptions;
400
-
401
- private input = new Input();
402
- private transcriptRenderer = new TranscriptRenderer();
403
- /** Scroll offset in lines from the bottom of the transcript. 0 = pinned to bottom. */
404
- private scrollOffset = 0;
398
+ private page: AgentSessionPage;
405
399
  private unsubscribe: () => void;
406
400
  private renderTimer?: ReturnType<typeof setTimeout>;
407
401
  private ticker?: ReturnType<typeof setInterval>;
408
402
  private closed = false;
409
403
 
410
- private _focused = false;
411
- get focused(): boolean {
412
- return this._focused;
404
+ get focused() {
405
+ return this.page.focused;
413
406
  }
414
407
  set focused(value: boolean) {
415
- this._focused = value;
416
- this.input.focused = value;
408
+ this.page.focused = value;
417
409
  }
418
410
 
419
411
  constructor(
@@ -425,46 +417,58 @@ export class TakeoverView implements Component, Focusable {
425
417
  done: (value: null) => void,
426
418
  options?: TakeoverOptions,
427
419
  ) {
428
- this.tui = tui;
429
- this.theme = theme;
430
- this.keybindings = keybindings;
431
- this.id = id;
432
- this.view = view;
433
- this.done = done;
434
- this.options = options;
420
+ this.page = new AgentSessionPage(
421
+ tui,
422
+ theme,
423
+ keybindings,
424
+ {
425
+ getState: () => {
426
+ const snap = view.get(id);
427
+ if (!snap) return undefined;
428
+ return {
429
+ id: snap.id,
430
+ title: snap.title,
431
+ status: snap.status,
432
+ document: subagentTranscriptDocument(
433
+ snap,
434
+ view.getToolRenderer?.(id),
435
+ ),
436
+ metadata: [
437
+ options?.badge,
438
+ snap.meta.modelLabel,
439
+ formatContextUtilization(snap.usage),
440
+ formatElapsed(snap),
441
+ ],
442
+ errorText: snap.errorText,
443
+ };
444
+ },
445
+ close: () => this.close(done),
446
+ abort: () => view.requestAbort(id),
447
+ },
448
+ { toolsExpanded: options?.toolsExpanded },
449
+ );
435
450
  this.unsubscribe = view.subscribeTo(id, () => {
436
- this.refreshTicker();
437
- this.scheduleRender();
451
+ this.refreshTicker(view.get(id), tui);
452
+ this.scheduleRender(tui);
438
453
  });
439
- this.refreshTicker();
440
- this.input.onSubmit = (value: string) => {
441
- const text = value.trim();
442
- if (!text) return;
443
- this.input.setValue("");
444
- this.view.requestSend(this.id, text);
445
- this.scrollOffset = 0;
446
- this.tui.requestRender();
447
- };
454
+ this.refreshTicker(view.get(id), tui);
448
455
  }
449
456
 
450
- private snap(): SubagentSnapshot | undefined {
451
- return this.view.get(this.id);
452
- }
453
-
454
- private refreshTicker() {
455
- const interval =
456
- this.snap()?.status === "running" ? SPINNER_INTERVAL_MS : 1000;
457
+ private refreshTicker(snap: SubagentSnapshot | undefined, tui: TUI) {
457
458
  if (this.ticker) clearInterval(this.ticker);
458
- this.ticker = setInterval(() => this.tui.requestRender(), interval);
459
+ this.ticker = undefined;
460
+ if (snap?.status === "running") {
461
+ this.ticker = setInterval(() => tui.requestRender(), SPINNER_INTERVAL_MS);
462
+ }
459
463
  }
460
464
 
461
- private scheduleRender() {
465
+ private scheduleRender(tui: TUI) {
462
466
  if (this.renderTimer) return;
463
467
  // Streaming can emit an event per token. Limit terminal repaints so this
464
468
  // view cannot starve input handling or make the child look frozen.
465
469
  this.renderTimer = setTimeout(() => {
466
470
  this.renderTimer = undefined;
467
- if (!this.closed) this.tui.requestRender();
471
+ if (!this.closed) tui.requestRender();
468
472
  }, 50);
469
473
  }
470
474
 
@@ -478,8 +482,8 @@ export class TakeoverView implements Component, Focusable {
478
482
  return true;
479
483
  }
480
484
 
481
- private close() {
482
- if (this.cleanup()) this.done(null);
485
+ private close(done: (value: null) => void) {
486
+ if (this.cleanup()) done(null);
483
487
  }
484
488
 
485
489
  dispose(): void {
@@ -487,202 +491,14 @@ export class TakeoverView implements Component, Focusable {
487
491
  }
488
492
 
489
493
  handleInput(data: string): void {
490
- if (this.keybindings.matches(data, "app.clear")) {
491
- const snap = this.snap();
492
- if (snap?.status === "running") this.view.requestAbort(this.id);
493
- return;
494
- }
495
- if (
496
- this.keybindings.matches(data, "app.interrupt") ||
497
- this.keybindings.matches(data, "tui.select.cancel")
498
- ) {
499
- this.close();
500
- return;
501
- }
502
- if (this.keybindings.matches(data, "tui.editor.cursorUp")) {
503
- this.scrollOffset += TRANSCRIPT_SCROLL_STEP;
504
- this.tui.requestRender();
505
- return;
506
- }
507
- if (this.keybindings.matches(data, "tui.editor.cursorDown")) {
508
- this.scrollOffset = Math.max(
509
- 0,
510
- this.scrollOffset - TRANSCRIPT_SCROLL_STEP,
511
- );
512
- this.tui.requestRender();
513
- return;
514
- }
515
- if (this.keybindings.matches(data, "tui.editor.pageUp")) {
516
- this.scrollOffset += this.viewportHeight();
517
- this.tui.requestRender();
518
- return;
519
- }
520
- if (this.keybindings.matches(data, "tui.editor.pageDown")) {
521
- this.scrollOffset = Math.max(
522
- 0,
523
- this.scrollOffset - this.viewportHeight(),
524
- );
525
- this.tui.requestRender();
526
- return;
527
- }
528
- this.input.handleInput(data);
529
- this.tui.requestRender();
530
- }
531
-
532
- private viewportHeight(): number {
533
- const rows = this.tui.terminal.rows || 30;
534
- // Top rule, transcript rule, input, key hints, and bottom rule are five
535
- // chrome rows. The overlay leaves Pi's final footer row visible.
536
- return Math.max(1, rows - 6);
537
- }
538
-
539
- private rule(width: number, left = "", right = "") {
540
- const fill = "─";
541
- const available = Math.max(1, width);
542
- const leftWidth = visibleWidth(left);
543
- const rightWidth = visibleWidth(right);
544
- if (!right || leftWidth + rightWidth + 2 > available) {
545
- return truncateToWidth(
546
- left + fill.repeat(Math.max(0, available - leftWidth)),
547
- available,
548
- );
549
- }
550
- return (
551
- left +
552
- fill.repeat(Math.max(1, available - leftWidth - rightWidth)) +
553
- right
554
- );
494
+ this.page.handleInput(data);
555
495
  }
556
496
 
557
497
  render(width: number): string[] {
558
- const theme = this.theme;
559
- const now = Date.now();
560
- const lines: string[] = [];
561
- const snap = this.snap();
562
-
563
- if (!snap) {
564
- const border = theme.fg("borderAccent", "─".repeat(Math.max(1, width)));
565
- lines.push(
566
- border,
567
- theme.fg("dim", `${this.id} is no longer tracked`),
568
- border,
569
- );
570
- return lines;
571
- }
572
-
573
- const title = sanitizeSubagentDisplayLine(snap.title) || snap.id;
574
- const headerLeft =
575
- theme.fg("borderAccent", "─ ") +
576
- statusGlyph(snap, theme, now) +
577
- " " +
578
- theme.fg("accent", theme.bold(title)) +
579
- theme.fg("borderAccent", " ");
580
- const utilization = formatContextUtilization(snap.usage);
581
- const metadata = [
582
- ...(this.options?.badge
583
- ? [theme.fg("muted", sanitizeSubagentDisplayLine(this.options.badge))]
584
- : []),
585
- theme.fg(
586
- "muted",
587
- sanitizeSubagentDisplayLine(snap.meta.modelLabel ?? "?") || "?",
588
- ),
589
- ...(utilization ? [theme.fg("muted", utilization)] : []),
590
- theme.fg("muted", formatElapsed(snap)),
591
- ];
592
- const dot = theme.fg("dim", " · ");
593
- while (
594
- metadata.length > 1 &&
595
- visibleWidth(headerLeft) + visibleWidth(metadata.join(dot)) + 2 > width
596
- ) {
597
- metadata.shift();
598
- }
599
- lines.push(
600
- this.rule(
601
- width,
602
- truncateToWidth(
603
- headerLeft,
604
- Math.max(1, width - visibleWidth(metadata.join(dot)) - 2),
605
- ),
606
- metadata.join(dot),
607
- ),
608
- );
609
-
610
- // Fixed-height transcript viewport. Errors consume a row, but scroll state
611
- // is represented by the following rule so its height never changes.
612
- // `now` is shared with the header glyph so both spinners show one frame.
613
- const transcript = buildTranscriptLines(
614
- snap,
615
- width,
616
- theme,
617
- this.transcriptRenderer,
618
- { now },
619
- );
620
- const viewport = this.viewportHeight();
621
- const errorRows = snap.errorText ? 1 : 0;
622
- const transcriptCapacity = Math.max(1, viewport - errorRows);
623
- const maxOffset = Math.max(0, transcript.length - transcriptCapacity);
624
- if (this.scrollOffset > maxOffset) this.scrollOffset = maxOffset;
625
-
626
- const body: string[] = [];
627
- if (snap.errorText) {
628
- body.push(
629
- truncateToWidth(
630
- theme.fg(
631
- "error",
632
- `error: ${sanitizeSubagentDisplayLine(snap.errorText)}`,
633
- ),
634
- width,
635
- ),
636
- );
637
- }
638
- const end = transcript.length - this.scrollOffset;
639
- const visible = transcript.slice(
640
- Math.max(0, end - Math.max(1, viewport - body.length)),
641
- end,
642
- );
643
- if (visible.length === 0) body.push(theme.fg("dim", "waiting for output…"));
644
- else body.push(...visible);
645
- while (body.length < viewport) body.push("");
646
- lines.push(...body.slice(0, viewport));
647
-
648
- lines.push(
649
- this.rule(
650
- width,
651
- theme.fg("borderAccent", "─"),
652
- this.scrollOffset > 0 ? theme.fg("dim", `↓ ${this.scrollOffset}`) : "",
653
- ),
654
- );
655
- lines.push(...this.input.render(width));
656
- const keys = (binding: Parameters<KeybindingsManager["getKeys"]>[0]) =>
657
- configuredKeys(this.keybindings, binding);
658
- const editing: ScreenHint[] = [
659
- [keys("tui.input.submit"), "send"],
660
- [keys("app.interrupt"), "back"],
661
- [keys("app.clear"), "abort run"],
662
- [
663
- `${keys("tui.editor.cursorUp")}/${keys("tui.editor.cursorDown")}`,
664
- "scroll",
665
- ],
666
- ];
667
- // Same drop-the-page-hint fallback as before, measured on the styled line
668
- // so the keys-brighter-than-labels styling cannot change what fits.
669
- const full = hintLine(
670
- theme,
671
- [
672
- ...editing,
673
- [`${keys("tui.editor.pageUp")}/${keys("tui.editor.pageDown")}`, "page"],
674
- ],
675
- width,
676
- );
677
- lines.push(
678
- visibleWidth(full) <= width ? full : hintLine(theme, editing, width),
679
- );
680
- lines.push(this.rule(width, theme.fg("borderAccent", "─")));
681
- return lines;
498
+ return this.page.render(width);
682
499
  }
683
500
 
684
501
  invalidate(): void {
685
- this.input.invalidate();
686
- this.transcriptRenderer.invalidate();
502
+ this.page.invalidate();
687
503
  }
688
504
  }