pi-btw 0.2.0 → 0.2.1

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/README.md CHANGED
@@ -13,6 +13,7 @@ A small [pi](https://github.com/badlogic/pi-mono) extension that adds a `/btw` s
13
13
  - keeps a continuous BTW thread by default
14
14
  - supports `/btw:tangent` for a contextless side thread that does not inherit the current main-session conversation
15
15
  - opens a focused BTW modal shell with its own composer and transcript
16
+ - keeps the BTW overlay open while you switch focus back to the main editor with `Alt+/`
16
17
  - keeps BTW thread entries out of the main agent's future context
17
18
  - lets you inject the full thread, or a summary of it, back into the main agent
18
19
  - optionally saves an individual BTW exchange as a visible session note with `--save`
@@ -69,6 +70,13 @@ pi install /absolute/path/to/pi-btw
69
70
  - persists the BTW exchange as hidden thread state
70
71
  - with `--save`, also saves that single exchange as a visible session note
71
72
 
73
+ ## Overlay controls
74
+
75
+ - `Alt+/` toggles focus between BTW and the main editor without closing the overlay
76
+ - `Ctrl+Alt+W` is a fallback focus toggle for terminals that do not deliver `Alt+/` as a usable shortcut
77
+ - `Esc` still dismisses BTW immediately while the overlay is focused
78
+ - BTW now opens top-centered so the main session remains visible underneath it
79
+
72
80
  ### `/btw:new [question]`
73
81
 
74
82
  - clears the current BTW thread
package/extensions/btw.ts CHANGED
@@ -31,6 +31,11 @@ import {
31
31
  const BTW_MESSAGE_TYPE = "btw-note";
32
32
  const BTW_ENTRY_TYPE = "btw-thread-entry";
33
33
  const BTW_RESET_TYPE = "btw-thread-reset";
34
+ const BTW_FOCUS_SHORTCUTS = [Key.alt("/"), Key.ctrlAlt("w")] as const;
35
+
36
+ function matchesBtwFocusShortcut(data: string): boolean {
37
+ return BTW_FOCUS_SHORTCUTS.some((shortcut) => matchesKey(data, shortcut));
38
+ }
34
39
 
35
40
  const BTW_SYSTEM_PROMPT = [
36
41
  "You are having an aside conversation with the user, separate from their main working session.",
@@ -940,6 +945,7 @@ class BtwOverlayComponent extends Container implements Focusable {
940
945
  private readonly getMode: () => BtwThreadMode;
941
946
  private readonly onSubmitCallback: (value: string) => void;
942
947
  private readonly onDismissCallback: () => void;
948
+ private readonly onUnfocusCallback: () => void;
943
949
  private readonly tui: TUI;
944
950
  private readonly theme: ExtensionContext["ui"]["theme"];
945
951
  private transcriptLines: string[] = [];
@@ -966,6 +972,7 @@ class BtwOverlayComponent extends Container implements Focusable {
966
972
  getMode: () => BtwThreadMode,
967
973
  onSubmit: (value: string) => void,
968
974
  onDismiss: () => void,
975
+ onUnfocus: () => void,
969
976
  ) {
970
977
  super();
971
978
  this.tui = tui;
@@ -975,6 +982,7 @@ class BtwOverlayComponent extends Container implements Focusable {
975
982
  this.getMode = getMode;
976
983
  this.onSubmitCallback = onSubmit;
977
984
  this.onDismissCallback = onDismiss;
985
+ this.onUnfocusCallback = onUnfocus;
978
986
 
979
987
  this.modeText = new Text("", 1, 0);
980
988
  this.summaryText = new Text("", 1, 0);
@@ -1034,10 +1042,15 @@ class BtwOverlayComponent extends Container implements Focusable {
1034
1042
 
1035
1043
  private getDialogHeight(): number {
1036
1044
  const terminalRows = process.stdout.rows ?? 30;
1037
- return Math.max(16, Math.min(24, Math.floor(terminalRows * 0.7)));
1045
+ return Math.max(18, Math.min(32, Math.floor(terminalRows * 0.78)));
1038
1046
  }
1039
1047
 
1040
1048
  handleInput(data: string): void {
1049
+ if (matchesBtwFocusShortcut(data)) {
1050
+ this.onUnfocusCallback();
1051
+ return;
1052
+ }
1053
+
1041
1054
  if (matchesKey(data, Key.pageUp)) {
1042
1055
  this.followTranscript = false;
1043
1056
  this.transcriptScrollOffset = Math.max(0, this.transcriptScrollOffset - Math.max(1, this.transcriptViewportHeight - 1));
@@ -1151,7 +1164,7 @@ class BtwOverlayComponent extends Container implements Focusable {
1151
1164
 
1152
1165
  const status = this.getStatus() ?? "Ready. Enter submits; Escape dismisses without clearing.";
1153
1166
  this.statusText.setText(status);
1154
- this.hintsText.setText("Enter submit · Escape dismiss · PgUp/PgDn scroll · /btw:clear resets thread");
1167
+ this.hintsText.setText("Enter submit · Alt+/ toggle focus · Escape dismiss · PgUp/PgDn scroll");
1155
1168
  this.tui.requestRender();
1156
1169
  }
1157
1170
  }
@@ -1189,6 +1202,32 @@ export default function (pi: ExtensionAPI) {
1189
1202
  overlayRuntime = null;
1190
1203
  }
1191
1204
 
1205
+ function toggleOverlayFocus(): void {
1206
+ const handle = overlayRuntime?.handle;
1207
+ if (!handle) {
1208
+ return;
1209
+ }
1210
+
1211
+ handle.setHidden(false);
1212
+ if (handle.isFocused()) {
1213
+ handle.unfocus();
1214
+ } else {
1215
+ handle.focus();
1216
+ }
1217
+ overlayRuntime?.refresh?.();
1218
+ }
1219
+
1220
+ function focusOverlay(): void {
1221
+ const handle = overlayRuntime?.handle;
1222
+ if (!handle) {
1223
+ return;
1224
+ }
1225
+
1226
+ handle.setHidden(false);
1227
+ handle.focus();
1228
+ overlayRuntime?.refresh?.();
1229
+ }
1230
+
1192
1231
  function removeBtwSessionSubscription(sessionRuntime: BtwSessionRuntime, unsubscribe: () => void): void {
1193
1232
  if (!sessionRuntime.subscriptions.delete(unsubscribe)) {
1194
1233
  return;
@@ -1318,9 +1357,7 @@ export default function (pi: ExtensionAPI) {
1318
1357
 
1319
1358
  if (overlayRuntime?.handle) {
1320
1359
  subscribeOverlayToActiveBtwSession(ctx);
1321
- overlayRuntime.handle.setHidden(false);
1322
- overlayRuntime.handle.focus();
1323
- overlayRuntime.refresh?.();
1360
+ focusOverlay();
1324
1361
  return;
1325
1362
  }
1326
1363
 
@@ -1363,6 +1400,10 @@ export default function (pi: ExtensionAPI) {
1363
1400
  () => {
1364
1401
  void dismissOverlaySession();
1365
1402
  },
1403
+ () => {
1404
+ overlayRuntime?.handle?.unfocus();
1405
+ overlayRuntime?.refresh?.();
1406
+ },
1366
1407
  );
1367
1408
 
1368
1409
  overlay.focused = runtime.handle?.isFocused() ?? true;
@@ -1393,8 +1434,9 @@ export default function (pi: ExtensionAPI) {
1393
1434
  width: "78%",
1394
1435
  minWidth: 72,
1395
1436
  maxHeight: "78%",
1396
- anchor: "center",
1397
- margin: 1,
1437
+ anchor: "top-center",
1438
+ margin: { top: 1, left: 2, right: 2 },
1439
+ nonCapturing: true,
1398
1440
  },
1399
1441
  onHandle: (handle) => {
1400
1442
  runtime.handle = handle;
@@ -1836,6 +1878,15 @@ export default function (pi: ExtensionAPI) {
1836
1878
  dismissOverlay();
1837
1879
  });
1838
1880
 
1881
+ for (const shortcut of BTW_FOCUS_SHORTCUTS) {
1882
+ pi.registerShortcut(shortcut, {
1883
+ description: "Toggle BTW overlay focus while leaving it open.",
1884
+ handler: async (_args, _ctx) => {
1885
+ toggleOverlayFocus();
1886
+ },
1887
+ });
1888
+ }
1889
+
1839
1890
  pi.registerCommand("btw", {
1840
1891
  description: "Continue a side conversation in a focused BTW modal. Add --save to also persist a visible note.",
1841
1892
  handler: async (args, ctx) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-btw",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "A pi extension for parallel side conversations with /btw",
5
5
  "type": "module",
6
6
  "license": "MIT",