@xynogen/pix-core 0.2.4 → 0.3.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.
- package/package.json +11 -17
- package/skills/ask-user/SKILL.md +0 -48
- package/src/commands/agent-sop/agent-sop.ts +0 -58
- package/src/commands/clear/clear.ts +0 -32
- package/src/commands/diff/diff.ts +0 -32
- package/src/commands/models/models.test.ts +0 -95
- package/src/commands/models/models.ts +0 -367
- package/src/commands/models/patch-builtin.test.ts +0 -66
- package/src/commands/models/patch-builtin.ts +0 -120
- package/src/commands/tools.test.ts +0 -15
- package/src/commands/update/update.test.ts +0 -112
- package/src/commands/update/update.ts +0 -271
- package/src/index.ts +0 -45
- package/src/lib/data.ts +0 -33
- package/src/nudge/capability.test.ts +0 -258
- package/src/nudge/capability.ts +0 -189
- package/src/nudge/index.ts +0 -17
- package/src/nudge/tools.test.ts +0 -157
- package/src/nudge/tools.ts +0 -212
- package/src/tool/ask/ask.test.ts +0 -243
- package/src/tool/ask/components.ts +0 -55
- package/src/tool/ask/helpers.ts +0 -77
- package/src/tool/ask/index.ts +0 -130
- package/src/tool/ask/questionnaire.ts +0 -693
- package/src/tool/ask/rpc.ts +0 -84
- package/src/tool/ask/schema.ts +0 -69
- package/src/tool/ask/single-select-layout.test.ts +0 -124
- package/src/tool/ask/single-select-layout.ts +0 -237
- package/src/tool/ask/types.ts +0 -17
- package/src/tool/todo/todo.test.ts +0 -646
- package/src/tool/todo/todo.ts +0 -218
- package/src/tool/toolbox/toolbox.test.ts +0 -314
- package/src/tool/toolbox/toolbox.ts +0 -570
- package/src/ui/diagnostics.ts +0 -145
- package/src/ui/footer.ts +0 -513
- package/src/ui/welcome.test.ts +0 -124
- package/src/ui/welcome.ts +0 -369
package/src/tool/ask/rpc.ts
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import type { Params } from "./schema.js";
|
|
2
|
-
import { SENTINEL_FREEFORM } from "./schema.js";
|
|
3
|
-
import type { QuestionAnswer, QuestionnaireResult } from "./types.js";
|
|
4
|
-
|
|
5
|
-
// ── RPC / non-TUI fallback ─────────────────────────────────────────────
|
|
6
|
-
// Used when ctx.hasUI is false (headless / JSON / print mode).
|
|
7
|
-
|
|
8
|
-
export async function rpcFallback(
|
|
9
|
-
ui: { select: Function; input: Function },
|
|
10
|
-
params: Params,
|
|
11
|
-
): Promise<QuestionnaireResult> {
|
|
12
|
-
const answers: QuestionAnswer[] = [];
|
|
13
|
-
let cancelled = false;
|
|
14
|
-
|
|
15
|
-
for (let i = 0; i < params.questions.length; i++) {
|
|
16
|
-
const q = params.questions[i]!;
|
|
17
|
-
const header = q.header;
|
|
18
|
-
|
|
19
|
-
if (q.multiSelect) {
|
|
20
|
-
const lines = q.options.map(
|
|
21
|
-
(o, idx) => `${idx + 1}. ${o.label} — ${o.description}`,
|
|
22
|
-
);
|
|
23
|
-
const raw = await ui.input(
|
|
24
|
-
`${header}: ${q.question}\n\n${lines.join("\n")}\n\nEnter numbers separated by commas:`,
|
|
25
|
-
"e.g. 1,3",
|
|
26
|
-
);
|
|
27
|
-
if (raw == null) {
|
|
28
|
-
cancelled = true;
|
|
29
|
-
break;
|
|
30
|
-
}
|
|
31
|
-
const indices = String(raw)
|
|
32
|
-
.split(",")
|
|
33
|
-
.map((s) => Number(s.trim()))
|
|
34
|
-
.filter((n) => n >= 1 && n <= q.options.length);
|
|
35
|
-
const selected = indices.map((n) => q.options[n - 1]?.label);
|
|
36
|
-
if (selected.length > 0) {
|
|
37
|
-
answers.push({
|
|
38
|
-
questionIndex: i,
|
|
39
|
-
question: q.question,
|
|
40
|
-
kind: "multi",
|
|
41
|
-
answer: null,
|
|
42
|
-
selected,
|
|
43
|
-
});
|
|
44
|
-
} else {
|
|
45
|
-
cancelled = true;
|
|
46
|
-
break;
|
|
47
|
-
}
|
|
48
|
-
} else {
|
|
49
|
-
const items = q.options.map((o) => `${o.label} — ${o.description}`);
|
|
50
|
-
items.push(SENTINEL_FREEFORM);
|
|
51
|
-
const chosen = await ui.select(`${header}: ${q.question}`, items);
|
|
52
|
-
if (chosen == null) {
|
|
53
|
-
cancelled = true;
|
|
54
|
-
break;
|
|
55
|
-
}
|
|
56
|
-
if (chosen === SENTINEL_FREEFORM) {
|
|
57
|
-
const text = await ui.input(q.question, "Type your answer...");
|
|
58
|
-
if (text == null) {
|
|
59
|
-
cancelled = true;
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
answers.push({
|
|
63
|
-
questionIndex: i,
|
|
64
|
-
question: q.question,
|
|
65
|
-
kind: "custom",
|
|
66
|
-
answer: String(text),
|
|
67
|
-
});
|
|
68
|
-
} else {
|
|
69
|
-
const opt = q.options.find(
|
|
70
|
-
(o) =>
|
|
71
|
-
chosen === o.label || `${o.label} — ${o.description}` === chosen,
|
|
72
|
-
)!;
|
|
73
|
-
answers.push({
|
|
74
|
-
questionIndex: i,
|
|
75
|
-
question: q.question,
|
|
76
|
-
kind: "option",
|
|
77
|
-
answer: opt?.label ?? String(chosen),
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return { answers, cancelled };
|
|
84
|
-
}
|
package/src/tool/ask/schema.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { type Static, Type } from "typebox";
|
|
2
|
-
|
|
3
|
-
// ── Constants ──────────────────────────────────────────────────────────
|
|
4
|
-
|
|
5
|
-
export const MAX_QUESTIONS = 4;
|
|
6
|
-
export const MIN_OPTIONS = 2;
|
|
7
|
-
export const MAX_OPTIONS = 4;
|
|
8
|
-
export const MAX_HEADER_LENGTH = 16;
|
|
9
|
-
export const MAX_LABEL_LENGTH = 60;
|
|
10
|
-
|
|
11
|
-
export const SENTINEL_FREEFORM = "Type something.";
|
|
12
|
-
export const SENTINEL_CHAT = "Chat about this";
|
|
13
|
-
export const SENTINEL_NEXT = "Next";
|
|
14
|
-
|
|
15
|
-
export const SPLIT_PANE_MIN_WIDTH = 84;
|
|
16
|
-
export const SEPARATOR = " │ ";
|
|
17
|
-
|
|
18
|
-
// ── Schemas ────────────────────────────────────────────────────────────
|
|
19
|
-
|
|
20
|
-
export const OptionSchema = Type.Object({
|
|
21
|
-
label: Type.String({
|
|
22
|
-
maxLength: MAX_LABEL_LENGTH,
|
|
23
|
-
description: `MAX ${MAX_LABEL_LENGTH} CHARACTERS. Display text for this option. Concise (1-5 words).`,
|
|
24
|
-
}),
|
|
25
|
-
description: Type.String({
|
|
26
|
-
description: "Explanation of what this option means or trade-offs.",
|
|
27
|
-
}),
|
|
28
|
-
preview: Type.Optional(
|
|
29
|
-
Type.String({
|
|
30
|
-
description:
|
|
31
|
-
"Optional markdown preview for side-by-side layout (single-select only).",
|
|
32
|
-
}),
|
|
33
|
-
),
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
export const QuestionSchema = Type.Object({
|
|
37
|
-
question: Type.String({
|
|
38
|
-
description: "Clear, specific question ending with ?",
|
|
39
|
-
}),
|
|
40
|
-
header: Type.String({
|
|
41
|
-
maxLength: MAX_HEADER_LENGTH,
|
|
42
|
-
description: `MAX ${MAX_HEADER_LENGTH} CHARS — short chip/tag. E.g. "Auth method", "Approach".`,
|
|
43
|
-
}),
|
|
44
|
-
options: Type.Array(OptionSchema, {
|
|
45
|
-
minItems: MIN_OPTIONS,
|
|
46
|
-
maxItems: MAX_OPTIONS,
|
|
47
|
-
description:
|
|
48
|
-
"2-4 options. 'Type something.' and 'Chat about this' are auto-appended.",
|
|
49
|
-
}),
|
|
50
|
-
multiSelect: Type.Optional(
|
|
51
|
-
Type.Boolean({
|
|
52
|
-
default: false,
|
|
53
|
-
description:
|
|
54
|
-
"Allow multiple selections. Suppresses 'Type something.' row.",
|
|
55
|
-
}),
|
|
56
|
-
),
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
export const QuestionsSchema = Type.Array(QuestionSchema, {
|
|
60
|
-
minItems: 1,
|
|
61
|
-
maxItems: MAX_QUESTIONS,
|
|
62
|
-
description: "1-4 questions",
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
export const ParamsSchema = Type.Object({ questions: QuestionsSchema });
|
|
66
|
-
|
|
67
|
-
export type OptionData = Static<typeof OptionSchema>;
|
|
68
|
-
export type QuestionData = Static<typeof QuestionSchema>;
|
|
69
|
-
export type Params = Static<typeof ParamsSchema>;
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { renderSingleSelectRows } from "./single-select-layout";
|
|
3
|
-
|
|
4
|
-
describe("renderSingleSelectRows", () => {
|
|
5
|
-
test("wraps long option titles instead of truncating them away", () => {
|
|
6
|
-
const rows = renderSingleSelectRows({
|
|
7
|
-
options: [
|
|
8
|
-
{
|
|
9
|
-
title:
|
|
10
|
-
"I want help with a coding or implementation task that involves changing, creating, reviewing, refactoring, or understanding code in a project",
|
|
11
|
-
},
|
|
12
|
-
],
|
|
13
|
-
selectedIndex: 0,
|
|
14
|
-
width: 40,
|
|
15
|
-
allowFreeform: false,
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
expect(rows.length).toBeGreaterThan(1);
|
|
19
|
-
expect(rows.map((r) => r.line).join(" ")).toContain("implementation task");
|
|
20
|
-
expect(rows.map((r) => r.line).join(" ")).toContain("understanding code");
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
test("wraps long descriptions under their option instead of clipping them", () => {
|
|
24
|
-
const rows = renderSingleSelectRows({
|
|
25
|
-
options: [
|
|
26
|
-
{
|
|
27
|
-
title: "Planning help",
|
|
28
|
-
description:
|
|
29
|
-
"Choose this if you are still deciding what to do, want a plan first, need architecture guidance, or want to evaluate alternatives before touching code.",
|
|
30
|
-
},
|
|
31
|
-
],
|
|
32
|
-
selectedIndex: 0,
|
|
33
|
-
width: 44,
|
|
34
|
-
allowFreeform: false,
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
const rendered = rows
|
|
38
|
-
.map((r) => r.line)
|
|
39
|
-
.join(" ")
|
|
40
|
-
.replace(/\s+/g, " ")
|
|
41
|
-
.trim();
|
|
42
|
-
expect(rendered).toContain("want a plan first");
|
|
43
|
-
expect(rendered).toContain("before touching code");
|
|
44
|
-
expect(rows.length).toBeGreaterThan(2);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test("caps the rendered rows and keeps the selected option visible when content is taller than the viewport", () => {
|
|
48
|
-
const rows = renderSingleSelectRows({
|
|
49
|
-
options: [
|
|
50
|
-
{
|
|
51
|
-
title:
|
|
52
|
-
"I want help with a coding or implementation task that involves changing, creating, reviewing, refactoring, or understanding code in a project",
|
|
53
|
-
description:
|
|
54
|
-
"Choose this if your main goal is to build something, fix code, understand existing code, add a feature, improve architecture, write tests, or get help with development work.",
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
title:
|
|
58
|
-
"I want help troubleshooting, debugging, diagnosing, reproducing, isolating, or explaining a bug, failure, regression, flaky test, unexpected behavior, runtime error, build issue, deployment problem, configuration mistake, performance bottleneck, or environment-specific issue",
|
|
59
|
-
description:
|
|
60
|
-
"Choose this if something is broken, inconsistent, failing, slow, confusing, or behaving differently than expected and you want systematic help narrowing it down.",
|
|
61
|
-
},
|
|
62
|
-
],
|
|
63
|
-
selectedIndex: 1,
|
|
64
|
-
width: 44,
|
|
65
|
-
allowFreeform: false,
|
|
66
|
-
maxRows: 6,
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
expect(rows.length).toBeLessThanOrEqual(6);
|
|
70
|
-
expect(
|
|
71
|
-
rows
|
|
72
|
-
.map((r) => r.line)
|
|
73
|
-
.join(" ")
|
|
74
|
-
.replace(/\s+/g, " "),
|
|
75
|
-
).toContain("troubleshooting");
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test("does not duplicate a short word after wrapping an exact-width long word", () => {
|
|
79
|
-
const rows = renderSingleSelectRows({
|
|
80
|
-
options: [
|
|
81
|
-
{
|
|
82
|
-
title: "Alpha",
|
|
83
|
-
description: "hi aaaaaaaaaaaaaaaa",
|
|
84
|
-
},
|
|
85
|
-
],
|
|
86
|
-
selectedIndex: 0,
|
|
87
|
-
width: 12,
|
|
88
|
-
allowFreeform: false,
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
expect(
|
|
92
|
-
rows.map((r) => r.line).filter((line) => line.trim() === "hi"),
|
|
93
|
-
).toHaveLength(1);
|
|
94
|
-
expect(
|
|
95
|
-
rows.map((r) => r.line).filter((line) => line.trim() === "aaaaaaaa"),
|
|
96
|
-
).toHaveLength(2);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test("marks selected item rows as selected in annotated output", () => {
|
|
100
|
-
const rows = renderSingleSelectRows({
|
|
101
|
-
options: [
|
|
102
|
-
{ title: "Alpha" },
|
|
103
|
-
{
|
|
104
|
-
title:
|
|
105
|
-
"Beta with a very long title that should wrap to multiple lines when rendered",
|
|
106
|
-
},
|
|
107
|
-
{ title: "Gamma" },
|
|
108
|
-
],
|
|
109
|
-
selectedIndex: 1,
|
|
110
|
-
width: 30,
|
|
111
|
-
allowFreeform: false,
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
const selectedRows = rows.filter((r) => r.selected);
|
|
115
|
-
const nonSelectedRows = rows.filter((r) => !r.selected);
|
|
116
|
-
|
|
117
|
-
expect(selectedRows.length).toBeGreaterThan(1);
|
|
118
|
-
for (const row of selectedRows) {
|
|
119
|
-
expect(row.line).not.toContain("Alpha");
|
|
120
|
-
expect(row.line).not.toContain("Gamma");
|
|
121
|
-
}
|
|
122
|
-
expect(nonSelectedRows.length).toBeGreaterThan(0);
|
|
123
|
-
});
|
|
124
|
-
});
|
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
export interface QuestionOption {
|
|
2
|
-
title: string;
|
|
3
|
-
description?: string;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export interface AnnotatedRow {
|
|
7
|
-
line: string;
|
|
8
|
-
selected: boolean;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface RenderSingleSelectRowsParams {
|
|
12
|
-
options: QuestionOption[];
|
|
13
|
-
selectedIndex: number;
|
|
14
|
-
width: number;
|
|
15
|
-
allowFreeform: boolean;
|
|
16
|
-
allowComment?: boolean;
|
|
17
|
-
commentEnabled?: boolean;
|
|
18
|
-
maxRows?: number;
|
|
19
|
-
hideDescriptions?: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function wrapText(text: string, width: number): string[] {
|
|
23
|
-
const normalized = text.replace(/\s+/g, " ").trim();
|
|
24
|
-
if (!normalized) return [""];
|
|
25
|
-
if (width <= 1) return normalized.split("");
|
|
26
|
-
|
|
27
|
-
const words = normalized.split(" ");
|
|
28
|
-
const lines: string[] = [];
|
|
29
|
-
let current = "";
|
|
30
|
-
|
|
31
|
-
for (const word of words) {
|
|
32
|
-
if (!current) {
|
|
33
|
-
if (word.length <= width) {
|
|
34
|
-
current = word;
|
|
35
|
-
} else {
|
|
36
|
-
for (let i = 0; i < word.length; i += width) {
|
|
37
|
-
lines.push(word.slice(i, i + width));
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const candidate = `${current} ${word}`;
|
|
44
|
-
if (candidate.length <= width) {
|
|
45
|
-
current = candidate;
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
lines.push(current);
|
|
50
|
-
if (word.length <= width) {
|
|
51
|
-
current = word;
|
|
52
|
-
} else {
|
|
53
|
-
current = "";
|
|
54
|
-
for (let i = 0; i < word.length; i += width) {
|
|
55
|
-
const chunk = word.slice(i, i + width);
|
|
56
|
-
if (chunk.length === width || i + width < word.length)
|
|
57
|
-
lines.push(chunk);
|
|
58
|
-
else current = chunk;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (current) lines.push(current);
|
|
64
|
-
return lines;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function padLine(prefix: string, content: string): string {
|
|
68
|
-
return `${prefix}${content}`.trimEnd();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
interface ItemBlock {
|
|
72
|
-
itemIndex: number;
|
|
73
|
-
lines: string[];
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
type ListItem =
|
|
77
|
-
| { type: "option"; option: QuestionOption }
|
|
78
|
-
| { type: "comment-toggle"; option: QuestionOption }
|
|
79
|
-
| { type: "freeform"; option: QuestionOption };
|
|
80
|
-
|
|
81
|
-
function buildItemBlocks(
|
|
82
|
-
options: QuestionOption[],
|
|
83
|
-
width: number,
|
|
84
|
-
allowFreeform: boolean,
|
|
85
|
-
allowComment: boolean,
|
|
86
|
-
commentEnabled: boolean,
|
|
87
|
-
selectedIndex: number,
|
|
88
|
-
hideDescriptions = false,
|
|
89
|
-
): ItemBlock[] {
|
|
90
|
-
const normalizedWidth = Math.max(12, width);
|
|
91
|
-
const freeformLabel = "Type something. — Enter a custom response";
|
|
92
|
-
const commentToggleLabel = `${commentEnabled ? "[✓]" : "[ ]"} Add extra context after selection`;
|
|
93
|
-
const allItems: ListItem[] = options.map((option) => ({
|
|
94
|
-
type: "option",
|
|
95
|
-
option,
|
|
96
|
-
}));
|
|
97
|
-
if (allowComment) {
|
|
98
|
-
allItems.push({
|
|
99
|
-
type: "comment-toggle",
|
|
100
|
-
option: { title: commentToggleLabel },
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
if (allowFreeform) {
|
|
104
|
-
allItems.push({ type: "freeform", option: { title: freeformLabel } });
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return allItems.map((item, itemIndex) => {
|
|
108
|
-
const pointer = itemIndex === selectedIndex ? "→" : " ";
|
|
109
|
-
const lines: string[] = [];
|
|
110
|
-
|
|
111
|
-
if (item.type === "comment-toggle" || item.type === "freeform") {
|
|
112
|
-
const prefix = `${pointer} `;
|
|
113
|
-
const wrapped = wrapText(
|
|
114
|
-
item.option.title,
|
|
115
|
-
Math.max(8, normalizedWidth - prefix.length),
|
|
116
|
-
);
|
|
117
|
-
wrapped.forEach((line, lineIndex) => {
|
|
118
|
-
lines.push(
|
|
119
|
-
padLine(lineIndex === 0 ? prefix : " ".repeat(prefix.length), line),
|
|
120
|
-
);
|
|
121
|
-
});
|
|
122
|
-
return { itemIndex, lines };
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const numberPrefix = `${pointer} ${itemIndex + 1}. `;
|
|
126
|
-
const continuationPrefix = " ".repeat(numberPrefix.length);
|
|
127
|
-
const titleLines = wrapText(
|
|
128
|
-
item.option.title,
|
|
129
|
-
Math.max(8, normalizedWidth - numberPrefix.length),
|
|
130
|
-
);
|
|
131
|
-
titleLines.forEach((line, lineIndex) => {
|
|
132
|
-
lines.push(
|
|
133
|
-
padLine(lineIndex === 0 ? numberPrefix : continuationPrefix, line),
|
|
134
|
-
);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
if (item.option.description && !hideDescriptions) {
|
|
138
|
-
const descriptionPrefix = " ";
|
|
139
|
-
const descriptionLines = wrapText(
|
|
140
|
-
item.option.description,
|
|
141
|
-
Math.max(8, normalizedWidth - descriptionPrefix.length),
|
|
142
|
-
);
|
|
143
|
-
descriptionLines.forEach((line) => {
|
|
144
|
-
lines.push(padLine(descriptionPrefix, line));
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return { itemIndex, lines };
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function flatten(blocks: ItemBlock[], selectedIndex: number): AnnotatedRow[] {
|
|
153
|
-
return blocks.flatMap((block) =>
|
|
154
|
-
block.lines.map((line) => ({
|
|
155
|
-
line,
|
|
156
|
-
selected: block.itemIndex === selectedIndex,
|
|
157
|
-
})),
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export function renderSingleSelectRows({
|
|
162
|
-
options,
|
|
163
|
-
selectedIndex,
|
|
164
|
-
width,
|
|
165
|
-
allowFreeform,
|
|
166
|
-
allowComment = false,
|
|
167
|
-
commentEnabled = false,
|
|
168
|
-
maxRows,
|
|
169
|
-
hideDescriptions,
|
|
170
|
-
}: RenderSingleSelectRowsParams): AnnotatedRow[] {
|
|
171
|
-
const itemCount =
|
|
172
|
-
options.length + (allowComment ? 1 : 0) + (allowFreeform ? 1 : 0);
|
|
173
|
-
const blocks = buildItemBlocks(
|
|
174
|
-
options,
|
|
175
|
-
width,
|
|
176
|
-
allowFreeform,
|
|
177
|
-
allowComment,
|
|
178
|
-
commentEnabled,
|
|
179
|
-
selectedIndex,
|
|
180
|
-
hideDescriptions,
|
|
181
|
-
);
|
|
182
|
-
const allRows = flatten(blocks, selectedIndex);
|
|
183
|
-
|
|
184
|
-
if (
|
|
185
|
-
!Number.isFinite(maxRows) ||
|
|
186
|
-
!maxRows ||
|
|
187
|
-
maxRows <= 0 ||
|
|
188
|
-
allRows.length <= maxRows
|
|
189
|
-
) {
|
|
190
|
-
return allRows;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const safeMaxRows = Math.max(1, Math.floor(maxRows));
|
|
194
|
-
const selectedBlock = blocks[selectedIndex] ?? blocks[0];
|
|
195
|
-
if (!selectedBlock) return [];
|
|
196
|
-
|
|
197
|
-
const indicator = ` (${selectedIndex + 1}/${itemCount})`;
|
|
198
|
-
const availableRows = safeMaxRows > 1 ? safeMaxRows - 1 : 1;
|
|
199
|
-
|
|
200
|
-
if (selectedBlock.lines.length >= availableRows) {
|
|
201
|
-
const visible = selectedBlock.lines.slice(0, availableRows).map((line) => ({
|
|
202
|
-
line,
|
|
203
|
-
selected: true,
|
|
204
|
-
}));
|
|
205
|
-
if (safeMaxRows > 1) visible.push({ line: indicator, selected: false });
|
|
206
|
-
return visible.slice(0, safeMaxRows);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
let start = selectedIndex;
|
|
210
|
-
let end = selectedIndex + 1;
|
|
211
|
-
let usedRows = selectedBlock.lines.length;
|
|
212
|
-
|
|
213
|
-
while (true) {
|
|
214
|
-
const nextCanFit =
|
|
215
|
-
end < blocks.length &&
|
|
216
|
-
usedRows + blocks[end]?.lines.length <= availableRows;
|
|
217
|
-
if (nextCanFit) {
|
|
218
|
-
usedRows += blocks[end]?.lines.length;
|
|
219
|
-
end += 1;
|
|
220
|
-
continue;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const prevCanFit =
|
|
224
|
-
start > 0 && usedRows + blocks[start - 1]?.lines.length <= availableRows;
|
|
225
|
-
if (prevCanFit) {
|
|
226
|
-
start -= 1;
|
|
227
|
-
usedRows += blocks[start]?.lines.length;
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
break;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const visible = flatten(blocks.slice(start, end), selectedIndex);
|
|
235
|
-
visible.push({ line: indicator, selected: false });
|
|
236
|
-
return visible.slice(0, safeMaxRows);
|
|
237
|
-
}
|
package/src/tool/ask/types.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
// ── Answer & result types ──────────────────────────────────────────────
|
|
2
|
-
|
|
3
|
-
export type AnswerKind = "option" | "custom" | "chat" | "multi";
|
|
4
|
-
|
|
5
|
-
export interface QuestionAnswer {
|
|
6
|
-
questionIndex: number;
|
|
7
|
-
question: string;
|
|
8
|
-
kind: AnswerKind;
|
|
9
|
-
answer: string | null;
|
|
10
|
-
selected?: string[];
|
|
11
|
-
preview?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface QuestionnaireResult {
|
|
15
|
-
answers: QuestionAnswer[];
|
|
16
|
-
cancelled: boolean;
|
|
17
|
-
}
|