@wrongstack/tui 1.0.27 → 1.0.28

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.
@@ -1,8 +1,39 @@
1
1
  import { type KeyEvent } from './components/input.js';
2
2
  import type { AppKeyHandlerOptions } from './key-handler-context.js';
3
3
  export type { AppKeyHandlerOptions } from './key-handler-context.js';
4
- /** Keyboard activity means the user has taken control of an armed automatic turn. */
5
- export declare function stopNextStepsAutoSubmitOnKey(cancel: () => void): void;
4
+ /** One delivered input event, as the host passes it to `handleKey`. */
5
+ export interface NextStepsTakeoverEvent {
6
+ /** Text payload of the event — empty for special keys and pointer reports. */
7
+ input: string;
8
+ key: KeyEvent;
9
+ /**
10
+ * Current composer buffer. The armed submit clears the composer when it
11
+ * fires, so text sitting here is what makes ANY event a takeover.
12
+ */
13
+ draft: string;
14
+ }
15
+ /**
16
+ * True when this event means the user has taken control of an armed automatic
17
+ * turn, i.e. the armed next-steps submit must be cancelled.
18
+ *
19
+ * Pointer reports are NOT keyboard activity: the input layer emits mouse
20
+ * press/release/wheel/move as `KeyEvent`s carrying `mouse` (see
21
+ * `components/input.tsx`, which builds them as `EMPTY_KEY` + `mouse`). Treating
22
+ * them as input made a plain click, a wheel scroll, or a drag through the
23
+ * transcript look like typing and silently killed the armed countdown, so the
24
+ * automatic turn never advanced.
25
+ *
26
+ * The same reasoning covers keyboard navigation: `PgUp`/`PgDn`, the caret
27
+ * keys, and the Ctrl+U/Ctrl+D paging chord move through the transcript exactly
28
+ * like the wheel does, so on an empty composer they leave the countdown armed
29
+ * too. Everything that can put text in the composer — typing, editing, pasting,
30
+ * ↑/↓ history recall, Tab accepting the suggestion, Enter — still cancels, and
31
+ * so does any event at all once the composer holds text, because the armed
32
+ * submit calls `clearDraft()` when it fires.
33
+ */
34
+ export declare function shouldStopNextStepsAutoSubmit(event: NextStepsTakeoverEvent): boolean;
35
+ /** Cancel an armed automatic turn when this event is a real user takeover. */
36
+ export declare function stopNextStepsAutoSubmitOnKey(cancel: () => void, event: NextStepsTakeoverEvent): void;
6
37
  /** Creates the terminal key host around focused overlay/input routers. */
7
38
  export declare function createAppKeyHandler(options: AppKeyHandlerOptions): (input: string, key: KeyEvent) => Promise<void>;
8
39
  //# sourceMappingURL=app-key-handler.d.ts.map
@@ -42,6 +42,13 @@ export interface AuthOAuthStrategyRow {
42
42
  providerId: string;
43
43
  label: string;
44
44
  description?: string | undefined;
45
+ /**
46
+ * Extended guidance, one entry per paragraph (mirrors
47
+ * `ProviderAuthStrategyMetadata.notes`). Unlike `description` this may be
48
+ * several sentences, so the oauth view renders it wrapped in the
49
+ * focused-strategy detail area, not inline on the row.
50
+ */
51
+ notes?: readonly string[] | undefined;
45
52
  }
46
53
  /** Strategy ids are runtime-extensible. */
47
54
  export type AuthOAuthKind = string;
package/dist/index.js CHANGED
@@ -42278,7 +42278,7 @@ function KanbanPanel({
42278
42278
  setPrompt({ ...prompt, buffer: prompt.buffer.slice(0, -1) });
42279
42279
  return;
42280
42280
  }
42281
- if (input && !key2.ctrl && !key2.meta && input >= " ") {
42281
+ if (input && !key2.ctrl && !key2.meta && input >= " " && !isLeakedMouseInput(input)) {
42282
42282
  setPrompt({ ...prompt, buffer: `${prompt.buffer}${input}` });
42283
42283
  }
42284
42284
  return;
@@ -47456,6 +47456,23 @@ function keySummary(keyRow) {
47456
47456
  const created = keyRow.createdAt ? ` ${keyRow.createdAt.slice(0, 10)}` : "";
47457
47457
  return `${keyRow.masked}${method}${created}`;
47458
47458
  }
47459
+ var MAX_OAUTH_NOTE_LINES = 8;
47460
+ function wrapNoteText(text, width) {
47461
+ const words = text.split(/\s+/).filter((w) => w.length > 0);
47462
+ if (words.length === 0) return [];
47463
+ const lines = [];
47464
+ let cur = words[0] ?? "";
47465
+ for (const word of words.slice(1)) {
47466
+ if (cur.length + 1 + word.length <= width) {
47467
+ cur += ` ${word}`;
47468
+ continue;
47469
+ }
47470
+ lines.push(cur);
47471
+ cur = word.length > width ? word.slice(0, width) : word;
47472
+ }
47473
+ lines.push(cur);
47474
+ return lines;
47475
+ }
47459
47476
  function renderRow(row, focused, i) {
47460
47477
  const marker = focused ? "\u203A" : " ";
47461
47478
  const rowColor = focused ? UI_COLORS.focused : void 0;
@@ -47646,7 +47663,21 @@ function AuthPanel({ panel, maxRows, columns }) {
47646
47663
  const size = useTerminalSize();
47647
47664
  const budget = maxRows ?? Math.max(8, size.rows - 7);
47648
47665
  const compact2 = budget < 14;
47649
- const extraRows = (panel.hint ? 1 : 0) + (panel.confirm ? compact2 ? 1 : 2 : 0) + (panel.input ? compact2 ? 1 : 2 : 0) + (panel.view === "catalog" ? compact2 ? 1 : 2 : 0) + (!compact2 && panel.view === "provider" ? 2 : 0) + (!compact2 && panel.view === "oauth" ? 3 : 0) + (!compact2 && panel.view === "list" && panel.providers.length === 0 ? 2 : 0) + (!compact2 && panel.view === "catalog" && panel.busy ? 2 : 0);
47666
+ const noteWidth = Math.max(20, (columns ?? size.columns) - 4);
47667
+ const oauthFocus = panel.view === "oauth" ? panel.oauthStrategies?.[panel.selected] : void 0;
47668
+ const oauthNoteLines = !compact2 && oauthFocus?.notes ? oauthFocus.notes.flatMap(
47669
+ (note, i) => (
47670
+ // Blank separator between paragraphs; a space (not '') so Ink
47671
+ // emits the row and the reserve below matches the render.
47672
+ (i > 0 ? [" "] : []).concat(wrapNoteText(note, noteWidth))
47673
+ )
47674
+ ).slice(
47675
+ 0,
47676
+ // 8 keeps at least one list row visible on any non-compact
47677
+ // budget: total = 8 border/title/legend/warning + N + 1 list.
47678
+ Math.min(MAX_OAUTH_NOTE_LINES, Math.max(0, budget - 8))
47679
+ ) : [];
47680
+ const extraRows = (panel.hint ? 1 : 0) + (panel.confirm ? compact2 ? 1 : 2 : 0) + (panel.input ? compact2 ? 1 : 2 : 0) + (panel.view === "catalog" ? compact2 ? 1 : 2 : 0) + (!compact2 && panel.view === "provider" ? 2 : 0) + (!compact2 && panel.view === "oauth" ? 3 + oauthNoteLines.length : 0) + (!compact2 && panel.view === "list" && panel.providers.length === 0 ? 2 : 0) + (!compact2 && panel.view === "catalog" && panel.busy ? 2 : 0);
47650
47681
  const maxVisible = Math.max(
47651
47682
  1,
47652
47683
  budget - 4 - extraRows - (panel.view === "flow" ? compact2 ? 0 : 1 : 2)
@@ -47698,6 +47729,7 @@ function AuthPanel({ panel, maxRows, columns }) {
47698
47729
  /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Text2, { color: UI_COLORS.warning, wrap: "truncate-end", children: "\u26A0 Subscription tokens used outside official clients may violate provider Terms \u2014" }),
47699
47730
  /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Text2, { color: UI_COLORS.warning, wrap: "truncate-end", children: "your account could be rate-limited or banned. An API key is the sanctioned path." })
47700
47731
  ] }) : null,
47732
+ oauthNoteLines.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Box2, { flexDirection: "column", children: oauthNoteLines.map((line, ni) => /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Text2, { dimColor: true, wrap: "truncate-end", children: line }, `on-${ni}`)) }) : null,
47701
47733
  panel.view === "catalog" ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Box2, { marginTop: compact2 ? 0 : 1, children: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(Text2, { wrap: "truncate-end", children: [
47702
47734
  /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Text2, { dimColor: true, children: "filter: " }),
47703
47735
  panel.filter.length > 0 ? panel.filter : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(Text2, { dimColor: true, children: "(type to search)" }),
@@ -48670,6 +48702,7 @@ function isClearConfirmation(value) {
48670
48702
  return value === "YES";
48671
48703
  }
48672
48704
  function clearConfirmationKeyResult(value, input, key2) {
48705
+ if (input && isLeakedMouseInput(input)) return { decision: null, value };
48673
48706
  if (key2.escape) return { decision: false, value };
48674
48707
  if (key2.ctrl || key2.meta) return { decision: null, value };
48675
48708
  if (key2.return) {
@@ -54856,6 +54889,7 @@ function RefineCountdownPanel({
54856
54889
  }, []);
54857
54890
  use_input_default((input, key2) => {
54858
54891
  if (resolvedRef.current) return;
54892
+ if (input && isLeakedMouseInput(input)) return;
54859
54893
  resolvedRef.current = true;
54860
54894
  const isBackspace = key2.backspace || key2.delete || input === "\x7F" || input === "\b";
54861
54895
  if (key2.escape || isBackspace) onDecision("cancel");
@@ -85646,8 +85680,19 @@ async function routePointerEvents(ctx, input, key2) {
85646
85680
  }
85647
85681
 
85648
85682
  // src/app-key-handler.ts
85649
- function stopNextStepsAutoSubmitOnKey(cancel) {
85650
- cancel();
85683
+ function isComposerEmptyNavigation(input, key2) {
85684
+ if (key2.pageUp || key2.pageDown || key2.home || key2.end) return true;
85685
+ if (key2.leftArrow || key2.rightArrow) return true;
85686
+ return key2.ctrl && (input === "u" || input === "d");
85687
+ }
85688
+ function shouldStopNextStepsAutoSubmit(event) {
85689
+ const { input, key: key2, draft } = event;
85690
+ if (draft !== "") return true;
85691
+ if (key2.mouse) return false;
85692
+ return !isComposerEmptyNavigation(input, key2);
85693
+ }
85694
+ function stopNextStepsAutoSubmitOnKey(cancel, event) {
85695
+ if (shouldStopNextStepsAutoSubmit(event)) cancel();
85651
85696
  }
85652
85697
  function createAppKeyHandler(options) {
85653
85698
  const {
@@ -85660,7 +85705,8 @@ function createAppKeyHandler(options) {
85660
85705
  terminalColumns,
85661
85706
  terminalRows,
85662
85707
  mainColumnWidth,
85663
- cancelNextStepsCountdown
85708
+ cancelNextStepsCountdown,
85709
+ draftRef
85664
85710
  } = options;
85665
85711
  const stdout = { columns: terminalColumns, rows: terminalRows };
85666
85712
  const historyWidth = mainColumnWidth > 0 ? mainColumnWidth : stdout?.columns ?? 80;
@@ -85679,7 +85725,11 @@ function createAppKeyHandler(options) {
85679
85725
  );
85680
85726
  const handleKey = async (input, key2) => {
85681
85727
  if (!key2.escape) ctx.lastEscAtRef.current = 0;
85682
- stopNextStepsAutoSubmitOnKey(cancelNextStepsCountdown);
85728
+ stopNextStepsAutoSubmitOnKey(cancelNextStepsCountdown, {
85729
+ input,
85730
+ key: key2,
85731
+ draft: draftRef.current.buffer
85732
+ });
85683
85733
  if (routeCtrlCEscalation(ctx, input, key2)) return;
85684
85734
  if (routeModalOverlay(ctx, input, key2)) {
85685
85735
  ctx.lastEscAtRef.current = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/tui",
3
- "version": "1.0.27",
3
+ "version": "1.0.28",
4
4
  "license": "MIT",
5
5
  "description": "WrongStack terminal UI: Ink-based interactive renderer with history, pickers, and slash menus.",
6
6
  "repository": {
@@ -26,12 +26,12 @@
26
26
  "!dist/**/*.map"
27
27
  ],
28
28
  "dependencies": {
29
- "@wrongstack/core": "1.0.27",
30
- "@wrongstack/kanban": "1.0.27",
31
- "@wrongstack/runtime": "1.0.27",
32
- "@wrongstack/sage": "1.0.27",
33
- "@wrongstack/sdd": "1.0.27",
34
- "@wrongstack/tools": "1.0.27",
29
+ "@wrongstack/core": "1.0.28",
30
+ "@wrongstack/kanban": "1.0.28",
31
+ "@wrongstack/runtime": "1.0.28",
32
+ "@wrongstack/sage": "1.0.28",
33
+ "@wrongstack/sdd": "1.0.28",
34
+ "@wrongstack/tools": "1.0.28",
35
35
  "string-width": "8.2.2",
36
36
  "wrap-ansi": "^10.0.1"
37
37
  },