auq-mcp-server 2.1.0 → 2.1.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/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auq-mcp-server",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "main": "dist/index.js",
5
5
  "bin": {
6
6
  "auq": "dist/bin/auq.js"
@@ -4,7 +4,7 @@ import { t } from "../../i18n/index.js";
4
4
  import { useConfig } from "../ConfigContext.js";
5
5
  import { useTheme } from "../ThemeContext.js";
6
6
  import { isRecommendedOption } from "../utils/recommended.js";
7
- import { fitToVisualWidth } from "../utils/visualWidth.js";
7
+ import { fitToVisualWidth, getVisualWidth, padToVisualWidth, } from "../utils/visualWidth.js";
8
8
  import { MultiLineTextInput } from "./MultiLineTextInput.js";
9
9
  // isRecommendedOption is imported from ../utils/recommended.js
10
10
  /**
@@ -23,6 +23,35 @@ export const OptionsList = ({ isFocused, onSelect, options, selectedOption, show
23
23
  const fitRow = (text) => {
24
24
  return fitToVisualWidth(text, rowWidth);
25
25
  };
26
+ // Wrap text to multiple lines respecting visual width and explicit newlines
27
+ const wrapText = (text, width) => {
28
+ // First split on explicit newlines, then wrap each segment by visual width
29
+ const segments = text.split("\n");
30
+ const lines = [];
31
+ for (const segment of segments) {
32
+ if (getVisualWidth(segment) <= width) {
33
+ lines.push(segment);
34
+ continue;
35
+ }
36
+ let currentLine = "";
37
+ let currentWidth = 0;
38
+ for (const char of segment) {
39
+ const charWidth = getVisualWidth(char);
40
+ if (currentWidth + charWidth > width && currentLine.length > 0) {
41
+ lines.push(currentLine);
42
+ currentLine = char;
43
+ currentWidth = charWidth;
44
+ }
45
+ else {
46
+ currentLine += char;
47
+ currentWidth += charWidth;
48
+ }
49
+ }
50
+ if (currentLine)
51
+ lines.push(currentLine);
52
+ }
53
+ return lines;
54
+ };
26
55
  // Calculate max index: include custom input and elaborate options if enabled
27
56
  // Options: [0..n-1] = regular options, [n] = custom input, [n+1] = elaborate
28
57
  const customInputIndex = options.length;
@@ -141,11 +170,41 @@ export const OptionsList = ({ isFocused, onSelect, options, selectedOption, show
141
170
  : `${isFocusedOption ? ">" : " "} ${option.label}${isSelected ? " ✓" : ""}${starSuffix}`;
142
171
  return (React.createElement(Box, { key: index, flexDirection: "column" },
143
172
  React.createElement(Text, { backgroundColor: rowBg, bold: isFocusedOption || isSelected, color: rowColor }, fitRow(mainLine)),
144
- option.description && (React.createElement(Text, { backgroundColor: isFocusedOption
145
- ? theme.components.options.focusedBg
146
- : isSelected
147
- ? theme.components.options.selectedBg
148
- : undefined, color: theme.components.options.description, dimColor: !isFocusedOption && !isSelected }, fitRow(` ${option.description}`)))));
173
+ option.description &&
174
+ (() => {
175
+ const descText = ` ${option.description}`;
176
+ const descBg = isFocusedOption
177
+ ? theme.components.options.focusedBg
178
+ : isSelected
179
+ ? theme.components.options.selectedBg
180
+ : undefined;
181
+ const wouldWrap = descText.includes("\n") ||
182
+ getVisualWidth(descText) > rowWidth;
183
+ if (isFocusedOption && wouldWrap) {
184
+ // Focused + multi-line: show full description wrapped across lines
185
+ const wrappedLines = wrapText(descText, rowWidth);
186
+ return (React.createElement(Box, { flexDirection: "column" }, wrappedLines.map((line, lineIdx) => (React.createElement(Text, { key: lineIdx, backgroundColor: descBg, color: theme.components.options.description, dimColor: false }, padToVisualWidth(line, rowWidth))))));
187
+ }
188
+ else if (!isFocusedOption && wouldWrap) {
189
+ // Not focused + would wrap: truncate to 1 line with "..."
190
+ const maxWidth = rowWidth - 3; // Reserve 3 chars for "..."
191
+ let result = "";
192
+ let width = 0;
193
+ for (const char of descText) {
194
+ const charWidth = getVisualWidth(char);
195
+ if (width + charWidth > maxWidth)
196
+ break;
197
+ result += char;
198
+ width += charWidth;
199
+ }
200
+ const finalText = `${result}...`;
201
+ return (React.createElement(Text, { backgroundColor: descBg, color: theme.components.options.description, dimColor: true }, padToVisualWidth(finalText, rowWidth)));
202
+ }
203
+ else {
204
+ // Fits in 1 line (focused or not): show normally with padding
205
+ return (React.createElement(Text, { backgroundColor: descBg, color: theme.components.options.description, dimColor: !isFocusedOption && !isSelected }, fitRow(descText)));
206
+ }
207
+ })()));
149
208
  }),
150
209
  showCustomInput && (React.createElement(Box, { marginTop: 0 },
151
210
  React.createElement(Box, { flexDirection: "column" },
@@ -1,4 +1,4 @@
1
- import { Box, useApp, useInput } from "ink";
1
+ import { Box, useApp, useInput, useStdout } from "ink";
2
2
  import React, { useEffect, useMemo, useRef, useState } from "react";
3
3
  import { t } from "../../i18n/index.js";
4
4
  import { ResponseFormatter } from "../../session/ResponseFormatter.js";
@@ -44,6 +44,10 @@ export const StepperView = ({ onComplete, onProgress, sessionId, sessionRequest,
44
44
  .map((value) => value.toString().padStart(2, "0"))
45
45
  .join(":");
46
46
  }, [elapsedSeconds]);
47
+ // Detect if content overflows terminal height to pause periodic re-renders
48
+ const { stdout } = useStdout();
49
+ const terminalRows = stdout?.rows ?? 24;
50
+ const [isOverflowing, setIsOverflowing] = useState(false);
47
51
  // Report progress when question index changes
48
52
  useEffect(() => {
49
53
  if (onProgress) {
@@ -151,13 +155,26 @@ export const StepperView = ({ onComplete, onProgress, sessionId, sessionRequest,
151
155
  setHasAnyRecommendedInSession(anyHasRecommended);
152
156
  }, [sessionId, sessionRequest.questions]);
153
157
  // Update elapsed time since session creation
158
+ // IMPORTANT: Pause when content overflows terminal to prevent scroll-snapping
154
159
  useEffect(() => {
160
+ if (isOverflowing)
161
+ return;
155
162
  const timer = setInterval(() => {
156
163
  const elapsed = Math.floor((Date.now() - sessionCreatedAt) / 1000);
157
164
  setElapsedSeconds(elapsed >= 0 ? elapsed : 0);
158
165
  }, 1000);
159
166
  return () => clearInterval(timer);
160
- }, [sessionCreatedAt]);
167
+ }, [sessionCreatedAt, isOverflowing]);
168
+ // Detect overflow: estimate content height vs terminal rows
169
+ useEffect(() => {
170
+ const currentQ = sessionRequest.questions[safeIndex];
171
+ const optionCount = currentQ?.options?.length ?? 0;
172
+ // Conservative estimate: header(2) + tabbar(3) + prompt(3) + options(2 each)
173
+ // + footer(2) + custom/elaborate(6) + padding(2)
174
+ const estimatedContentHeight = 2 + 3 + 3 + optionCount * 2 + 2 + 6 + 2;
175
+ const nextOverflow = estimatedContentHeight > terminalRows;
176
+ setIsOverflowing((prev) => (prev === nextOverflow ? prev : nextOverflow));
177
+ }, [safeIndex, sessionRequest.questions, terminalRows]);
161
178
  // Handle answer confirmation
162
179
  const handleConfirm = async (userAnswers) => {
163
180
  setSubmitting(true);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auq-mcp-server",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "main": "dist/index.js",
5
5
  "bin": {
6
6
  "auq": "dist/bin/auq.js"