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
|
@@ -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 &&
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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);
|