@selesai/code 0.10.1 → 0.11.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/CHANGELOG.md +23 -0
- package/dist/config.d.ts +5 -3
- package/dist/config.js +6 -4
- package/dist/core/skills.d.ts +2 -0
- package/dist/core/skills.js +1 -0
- package/dist/defaults/settings.json +0 -3
- package/dist/extensions/auto-session-name.test.ts +157 -0
- package/dist/extensions/auto-session-name.ts +96 -0
- package/dist/extensions/cost-reconcile.test.ts +76 -0
- package/dist/extensions/cost-reconcile.ts +278 -0
- package/dist/extensions/enable-readonly-tools.test.ts +11 -0
- package/dist/extensions/enable-readonly-tools.ts +7 -3
- package/dist/extensions/package.json +3 -0
- package/dist/extensions/pi-tool-display/CHANGELOG.md +329 -0
- package/dist/extensions/pi-tool-display/LICENSE +21 -0
- package/dist/extensions/pi-tool-display/README.md +417 -0
- package/dist/extensions/pi-tool-display/config/config.example.json +40 -0
- package/dist/extensions/pi-tool-display/index.ts +3 -0
- package/dist/extensions/pi-tool-display/package-lock.json +2555 -0
- package/dist/extensions/pi-tool-display/package.json +86 -0
- package/dist/extensions/pi-tool-display/src/agent-dir.ts +35 -0
- package/dist/extensions/pi-tool-display/src/ansi-utils.ts +116 -0
- package/dist/extensions/pi-tool-display/src/bash-display.ts +213 -0
- package/dist/extensions/pi-tool-display/src/capabilities.ts +88 -0
- package/dist/extensions/pi-tool-display/src/config-modal.ts +541 -0
- package/dist/extensions/pi-tool-display/src/config-store.ts +304 -0
- package/dist/extensions/pi-tool-display/src/debug-logger.ts +150 -0
- package/dist/extensions/pi-tool-display/src/diff-presentation.ts +72 -0
- package/dist/extensions/pi-tool-display/src/diff-renderer.ts +2517 -0
- package/dist/extensions/pi-tool-display/src/disposable.ts +34 -0
- package/dist/extensions/pi-tool-display/src/extension-lifecycle.ts +14 -0
- package/dist/extensions/pi-tool-display/src/index.ts +109 -0
- package/dist/extensions/pi-tool-display/src/line-width-safety.ts +93 -0
- package/dist/extensions/pi-tool-display/src/modal-icons.ts +57 -0
- package/dist/extensions/pi-tool-display/src/pending-diff-preview.ts +370 -0
- package/dist/extensions/pi-tool-display/src/presets.ts +117 -0
- package/dist/extensions/pi-tool-display/src/render-utils.ts +186 -0
- package/dist/extensions/pi-tool-display/src/settings-inspector-modal.ts +475 -0
- package/dist/extensions/pi-tool-display/src/thinking-label.ts +343 -0
- package/dist/extensions/pi-tool-display/src/tool-call-box.ts +181 -0
- package/dist/extensions/pi-tool-display/src/tool-metadata.ts +139 -0
- package/dist/extensions/pi-tool-display/src/tool-overrides.ts +2168 -0
- package/dist/extensions/pi-tool-display/src/types.ts +109 -0
- package/dist/extensions/pi-tool-display/src/user-message-box-markdown.ts +64 -0
- package/dist/extensions/pi-tool-display/src/user-message-box-native.ts +66 -0
- package/dist/extensions/pi-tool-display/src/user-message-box-patch.ts +72 -0
- package/dist/extensions/pi-tool-display/src/user-message-box-renderer.ts +419 -0
- package/dist/extensions/pi-tool-display/src/user-message-box-utils.ts +106 -0
- package/dist/extensions/pi-tool-display/src/write-display-utils.ts +31 -0
- package/dist/extensions/pi-tool-display/src/zellij-modal.ts +1027 -0
- package/dist/extensions/pi-tool-display/tests/active-backlog-red.test.ts +65 -0
- package/dist/extensions/pi-tool-display/tests/ansi-utils.test.ts +345 -0
- package/dist/extensions/pi-tool-display/tests/bash-display.test.ts +442 -0
- package/dist/extensions/pi-tool-display/tests/capabilities-edge.test.ts +360 -0
- package/dist/extensions/pi-tool-display/tests/capabilities.test.ts +54 -0
- package/dist/extensions/pi-tool-display/tests/config-modal.test.ts +360 -0
- package/dist/extensions/pi-tool-display/tests/config-store.test.ts +87 -0
- package/dist/extensions/pi-tool-display/tests/custom-tool-overrides.test.ts +415 -0
- package/dist/extensions/pi-tool-display/tests/debug-logger-edge.test.ts +630 -0
- package/dist/extensions/pi-tool-display/tests/debug-logger.test.ts +62 -0
- package/dist/extensions/pi-tool-display/tests/diff-renderer-ansi.test.ts +55 -0
- package/dist/extensions/pi-tool-display/tests/diff-renderer-edge.test.ts +886 -0
- package/dist/extensions/pi-tool-display/tests/diff-renderer-numbering.test.ts +135 -0
- package/dist/extensions/pi-tool-display/tests/diff-renderer-width.test.ts +454 -0
- package/dist/extensions/pi-tool-display/tests/diff-renderer-wrap.test.ts +169 -0
- package/dist/extensions/pi-tool-display/tests/index-integration.test.ts +354 -0
- package/dist/extensions/pi-tool-display/tests/pending-diff-preview.test.ts +79 -0
- package/dist/extensions/pi-tool-display/tests/presets-edge.test.ts +229 -0
- package/dist/extensions/pi-tool-display/tests/presets.test.ts +26 -0
- package/dist/extensions/pi-tool-display/tests/reload-behavior.test.ts +1241 -0
- package/dist/extensions/pi-tool-display/tests/render-utils.test.ts +710 -0
- package/dist/extensions/pi-tool-display/tests/settings-inspector.test.ts +450 -0
- package/dist/extensions/pi-tool-display/tests/thinking-label-edge.test.ts +904 -0
- package/dist/extensions/pi-tool-display/tests/thinking-label.test.ts +72 -0
- package/dist/extensions/pi-tool-display/tests/tool-call-box.test.ts +105 -0
- package/dist/extensions/pi-tool-display/tests/tool-overrides-config.test.ts +613 -0
- package/dist/extensions/pi-tool-display/tests/tool-overrides-mcp.test.ts +499 -0
- package/dist/extensions/pi-tool-display/tests/tool-overrides-registration.test.ts +315 -0
- package/dist/extensions/pi-tool-display/tests/tool-overrides-write-meta.test.ts +68 -0
- package/dist/extensions/pi-tool-display/tests/tool-ui-utils.test.ts +413 -0
- package/dist/extensions/pi-tool-display/tests/user-message-box-performance-red.test.ts +176 -0
- package/dist/extensions/pi-tool-display/tests/user-message-box.test.ts +908 -0
- package/dist/extensions/pi-tool-display/tests/zellij-modal.test.ts +848 -0
- package/dist/extensions/pi-tool-display/tool-display-api-consumer.d.ts +30 -0
- package/dist/extensions/pi-tool-display/tool-display-api-consumer.js +40 -0
- package/dist/extensions/pi-zentui/extensions/zentui/format.ts +46 -1
- package/dist/extensions/pi-zentui/extensions/zentui/index.ts +10 -0
- package/dist/extensions/pi-zentui/extensions/zentui/user-message-styles.ts +1 -1
- package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +3 -3
- package/dist/extensions/pi-zentui/test/format.test.ts +50 -2
- package/dist/modes/interactive/components/startup-box.d.ts +38 -0
- package/dist/modes/interactive/components/startup-box.js +90 -0
- package/dist/modes/interactive/interactive-mode.d.ts +3 -0
- package/dist/modes/interactive/interactive-mode.js +85 -122
- package/dist/skills/agent-browser/SKILL.md +1 -0
- package/dist/skills/batch-grill-me/SKILL.md +1 -0
- package/dist/skills/brandkit/SKILL.md +1 -0
- package/dist/skills/design-references/SKILL.md +1 -0
- package/dist/skills/design-taste-frontend/SKILL.md +1 -0
- package/dist/skills/design-taste-frontend-v1/SKILL.md +1 -0
- package/dist/skills/full-output-enforcement/SKILL.md +1 -0
- package/dist/skills/gpt-taste/SKILL.md +1 -0
- package/dist/skills/grill-me/SKILL.md +1 -0
- package/dist/skills/handoff/SKILL.md +1 -0
- package/dist/skills/handoff-text/SKILL.md +1 -0
- package/dist/skills/high-end-visual-design/SKILL.md +1 -0
- package/dist/skills/image-to-code/SKILL.md +1 -0
- package/dist/skills/imagegen-frontend-mobile/SKILL.md +1 -0
- package/dist/skills/imagegen-frontend-web/SKILL.md +1 -0
- package/dist/skills/implanger/SKILL.md +1 -0
- package/dist/skills/improve-codebase/SKILL.md +1 -0
- package/dist/skills/industrial-brutalist-ui/SKILL.md +1 -0
- package/dist/skills/minimalist-ui/SKILL.md +1 -0
- package/dist/skills/pi-subagents/SKILL.md +1 -0
- package/dist/skills/planger/SKILL.md +1 -0
- package/dist/skills/ponytail/SKILL.md +1 -0
- package/dist/skills/ponytail-audit/SKILL.md +1 -0
- package/dist/skills/ponytail-debt/SKILL.md +1 -0
- package/dist/skills/ponytail-gain/SKILL.md +1 -0
- package/dist/skills/ponytail-help/SKILL.md +1 -0
- package/dist/skills/ponytail-review/SKILL.md +1 -0
- package/dist/skills/redesign-existing-projects/SKILL.md +1 -0
- package/dist/skills/selesai-handoff/SKILL.md +1 -0
- package/dist/skills/stitch-design-taste/SKILL.md +1 -0
- package/dist/skills/unlazy/SKILL.md +1 -0
- package/dist/skills/web-design-guidelines/SKILL.md +1 -0
- package/dist/skills/workflow/SKILL.md +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { SplitPaneInspectorModal, type InspectorSettingItem } from "../src/settings-inspector-modal.ts";
|
|
5
|
+
import { DEFAULT_TOOL_DISPLAY_CONFIG } from "../src/types.ts";
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Helpers
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
const passThroughTheme = {
|
|
12
|
+
fg: (_color: string, text: string): string => text,
|
|
13
|
+
bold: (text: string): string => text,
|
|
14
|
+
} as Theme;
|
|
15
|
+
|
|
16
|
+
function makeMcpSettings(hasMcp: boolean, hasRtk: boolean): InspectorSettingItem[] {
|
|
17
|
+
const config = DEFAULT_TOOL_DISPLAY_CONFIG;
|
|
18
|
+
const items: InspectorSettingItem[] = [
|
|
19
|
+
{
|
|
20
|
+
id: "preset",
|
|
21
|
+
label: "Preset profile",
|
|
22
|
+
currentValue: "opencode",
|
|
23
|
+
values: ["opencode", "balanced", "verbose"],
|
|
24
|
+
inspectorTitle: "Preset Profile",
|
|
25
|
+
inspectorSummary: ["Determines the overall verbosity."],
|
|
26
|
+
inspectorOptions: ["opencode", "balanced", "verbose"],
|
|
27
|
+
searchTerms: ["verbosity", "profile"],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "readOutputMode",
|
|
31
|
+
label: "Read tool output",
|
|
32
|
+
currentValue: config.readOutputMode,
|
|
33
|
+
values: ["hidden", "summary", "preview"],
|
|
34
|
+
inspectorTitle: "Read Tool Output",
|
|
35
|
+
inspectorSummary: ["Controls read results."],
|
|
36
|
+
inspectorPath: "~/.pi/extensions/pi-tool-display/config.json",
|
|
37
|
+
searchTerms: ["file", "source"],
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
if (hasMcp) {
|
|
42
|
+
items.push({
|
|
43
|
+
id: "mcpOutputMode",
|
|
44
|
+
label: "MCP tool output",
|
|
45
|
+
currentValue: config.mcpOutputMode,
|
|
46
|
+
values: ["hidden", "summary", "preview"],
|
|
47
|
+
inspectorTitle: "MCP Tool Output",
|
|
48
|
+
inspectorSummary: ["Controls MCP output."],
|
|
49
|
+
inspectorOptions: ["hidden", "summary", "preview"],
|
|
50
|
+
inspectorAdvanced: ["Only when MCP is available."],
|
|
51
|
+
searchTerms: ["mcp", "proxy"],
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (hasRtk) {
|
|
56
|
+
items.push({
|
|
57
|
+
id: "showRtkCompactionHints",
|
|
58
|
+
label: "RTK compaction hints",
|
|
59
|
+
currentValue: "off",
|
|
60
|
+
values: ["on", "off"],
|
|
61
|
+
inspectorTitle: "RTK Compaction Hints",
|
|
62
|
+
inspectorSummary: ["Controls RTK hint visibility."],
|
|
63
|
+
searchTerms: ["rtk", "hints"],
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
items.push(
|
|
68
|
+
{
|
|
69
|
+
id: "previewLines",
|
|
70
|
+
label: "Preview lines",
|
|
71
|
+
currentValue: String(config.previewLines),
|
|
72
|
+
values: ["4", "8", "12", "20", "40"],
|
|
73
|
+
inspectorTitle: "Preview Lines",
|
|
74
|
+
inspectorSummary: ["Sets preview line count."],
|
|
75
|
+
inspectorOptions: ["4", "8", "12", "20", "40"],
|
|
76
|
+
inspectorPath: "~/.pi/extensions/pi-tool-display/config.json",
|
|
77
|
+
searchTerms: ["preview", "lines"],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: "enableNativeUserMessageBox",
|
|
81
|
+
label: "Native user message box",
|
|
82
|
+
currentValue: "on",
|
|
83
|
+
values: ["on", "off"],
|
|
84
|
+
inspectorTitle: "Native User Message Box",
|
|
85
|
+
inspectorSummary: ["Toggles bordered renderer."],
|
|
86
|
+
searchTerms: ["user", "message"],
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
return items;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function makeModal(
|
|
93
|
+
items: InspectorSettingItem[],
|
|
94
|
+
theme = passThroughTheme,
|
|
95
|
+
): SplitPaneInspectorModal {
|
|
96
|
+
return new SplitPaneInspectorModal(
|
|
97
|
+
{
|
|
98
|
+
getSettings: () => items,
|
|
99
|
+
onChange: (_id: string, _value: string) => {},
|
|
100
|
+
onClose: () => {},
|
|
101
|
+
},
|
|
102
|
+
theme,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// Tests
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
test("renders split layout above minimum width", () => {
|
|
111
|
+
const modal = makeModal(makeMcpSettings(true, true));
|
|
112
|
+
const lines = modal.render(120);
|
|
113
|
+
|
|
114
|
+
assert.ok(lines.length >= 4, `expected at least 4 lines, got ${lines.length}`);
|
|
115
|
+
|
|
116
|
+
const headerLine = lines[0] ?? "";
|
|
117
|
+
assert.ok(
|
|
118
|
+
headerLine.includes("Pi Tool Display Settings"),
|
|
119
|
+
"header should contain title",
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const footerLine = lines[lines.length - 1] ?? "";
|
|
123
|
+
assert.ok(footerLine.includes("Esc"), "footer should show Esc action");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("renders stacked layout when width is below SPLIT_PANE_MIN_WIDTH (84)", () => {
|
|
127
|
+
const modal = makeModal(makeMcpSettings(true, true));
|
|
128
|
+
const lines = modal.render(60);
|
|
129
|
+
|
|
130
|
+
assert.ok(lines.length >= 4, `expected at least 4 lines for stacked, got ${lines.length}`);
|
|
131
|
+
|
|
132
|
+
const joined = lines.join(" ");
|
|
133
|
+
assert.ok(
|
|
134
|
+
joined.includes("Pi Tool Display Settings"),
|
|
135
|
+
"title should appear in stacked layout",
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("includes MCP setting when MCP capability is available", () => {
|
|
140
|
+
const items = makeMcpSettings(true, false);
|
|
141
|
+
const ids = items.map((i) => i.id);
|
|
142
|
+
assert.ok(ids.includes("mcpOutputMode"), "should include MCP setting");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("excludes MCP setting when MCP capability is absent", () => {
|
|
146
|
+
const items = makeMcpSettings(false, false);
|
|
147
|
+
const ids = items.map((i) => i.id);
|
|
148
|
+
assert.equal(ids.includes("mcpOutputMode"), false, "should exclude MCP setting");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("includes RTK setting when RTK capability is available", () => {
|
|
152
|
+
const items = makeMcpSettings(false, true);
|
|
153
|
+
const ids = items.map((i) => i.id);
|
|
154
|
+
assert.ok(ids.includes("showRtkCompactionHints"), "should include RTK setting");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("excludes RTK setting when RTK capability is absent", () => {
|
|
158
|
+
const items = makeMcpSettings(false, false);
|
|
159
|
+
const ids = items.map((i) => i.id);
|
|
160
|
+
assert.equal(ids.includes("showRtkCompactionHints"), false, "should exclude RTK setting");
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("search filters items by label", () => {
|
|
164
|
+
const modal = makeModal(makeMcpSettings(true, true));
|
|
165
|
+
|
|
166
|
+
// Type "/" to activate search
|
|
167
|
+
modal.handleInput("/");
|
|
168
|
+
// Type "MCP" characters into search
|
|
169
|
+
modal.handleInput("M");
|
|
170
|
+
modal.handleInput("C");
|
|
171
|
+
modal.handleInput("P");
|
|
172
|
+
|
|
173
|
+
const lines = modal.render(120);
|
|
174
|
+
const joined = lines.join(" ");
|
|
175
|
+
assert.ok(joined.includes("MCP tool output"), "filtered results should contain MCP setting");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("search shows 'No matching settings' for non-matching query", () => {
|
|
179
|
+
const modal = makeModal(makeMcpSettings(false, false));
|
|
180
|
+
modal.handleInput("/");
|
|
181
|
+
modal.handleInput("x");
|
|
182
|
+
modal.handleInput("y");
|
|
183
|
+
modal.handleInput("z");
|
|
184
|
+
|
|
185
|
+
const lines = modal.render(120);
|
|
186
|
+
const joined = lines.join(" ");
|
|
187
|
+
assert.ok(
|
|
188
|
+
joined.includes("No matching") || joined.includes("matching"),
|
|
189
|
+
"should indicate no matches",
|
|
190
|
+
);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("handleInput cycles value with space", () => {
|
|
194
|
+
const onChange = new Array<{ id: string; value: string }>();
|
|
195
|
+
const modal = new SplitPaneInspectorModal(
|
|
196
|
+
{
|
|
197
|
+
getSettings: () => makeMcpSettings(false, false),
|
|
198
|
+
onChange: (id: string, value: string) => {
|
|
199
|
+
onChange.push({ id, value });
|
|
200
|
+
},
|
|
201
|
+
onClose: () => {},
|
|
202
|
+
},
|
|
203
|
+
passThroughTheme,
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// First item is "preset" with values ["opencode", "balanced", "verbose"]
|
|
207
|
+
modal.handleInput(" ");
|
|
208
|
+
|
|
209
|
+
assert.equal(onChange.length, 1);
|
|
210
|
+
assert.equal(onChange[0]?.id, "preset");
|
|
211
|
+
assert.equal(onChange[0]?.value, "balanced");
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("handleInput cycles value with Enter (\\r)", () => {
|
|
215
|
+
const onChange = new Array<{ id: string; value: string }>();
|
|
216
|
+
const modal = new SplitPaneInspectorModal(
|
|
217
|
+
{
|
|
218
|
+
getSettings: () => makeMcpSettings(false, false),
|
|
219
|
+
onChange: (id: string, value: string) => {
|
|
220
|
+
onChange.push({ id, value });
|
|
221
|
+
},
|
|
222
|
+
onClose: () => {},
|
|
223
|
+
},
|
|
224
|
+
passThroughTheme,
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
modal.handleInput("\r");
|
|
228
|
+
|
|
229
|
+
assert.equal(onChange.length, 1);
|
|
230
|
+
assert.equal(onChange[0]?.value, "balanced");
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("handleInput moves selection down with arrow key", () => {
|
|
234
|
+
const itemsArr: InspectorSettingItem[] = makeMcpSettings(false, false);
|
|
235
|
+
const onChange: Array<{ id: string; value: string }> = [];
|
|
236
|
+
const modal = new SplitPaneInspectorModal(
|
|
237
|
+
{
|
|
238
|
+
getSettings: () => itemsArr,
|
|
239
|
+
onChange: (id: string, value: string) => {
|
|
240
|
+
onChange.push({ id, value });
|
|
241
|
+
},
|
|
242
|
+
onClose: () => {},
|
|
243
|
+
},
|
|
244
|
+
passThroughTheme,
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
// Move down once then cycle
|
|
248
|
+
modal.handleInput("\x1b[B");
|
|
249
|
+
modal.handleInput(" ");
|
|
250
|
+
|
|
251
|
+
assert.equal(onChange.length, 1);
|
|
252
|
+
assert.notEqual(onChange[0]?.id, "preset", "should not cycle first item after moving down");
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test("handleInput moves selection up with arrow key", () => {
|
|
256
|
+
const items = makeMcpSettings(true, false);
|
|
257
|
+
const onChange: Array<{ id: string; value: string }> = [];
|
|
258
|
+
const modal = new SplitPaneInspectorModal(
|
|
259
|
+
{
|
|
260
|
+
getSettings: () => items,
|
|
261
|
+
onChange: (id: string, value: string) => {
|
|
262
|
+
onChange.push({ id, value });
|
|
263
|
+
},
|
|
264
|
+
onClose: () => {},
|
|
265
|
+
},
|
|
266
|
+
passThroughTheme,
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
// Move down then up should select first item
|
|
270
|
+
modal.handleInput("\x1b[B"); // down
|
|
271
|
+
modal.handleInput("\x1b[A"); // up
|
|
272
|
+
modal.handleInput(" ");
|
|
273
|
+
|
|
274
|
+
assert.equal(onChange[0]?.id, "preset");
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test("handleInput calls onClose on Escape key", () => {
|
|
278
|
+
let closed = false;
|
|
279
|
+
const modal = new SplitPaneInspectorModal(
|
|
280
|
+
{
|
|
281
|
+
getSettings: () => makeMcpSettings(false, false),
|
|
282
|
+
onChange: () => {},
|
|
283
|
+
onClose: () => {
|
|
284
|
+
closed = true;
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
passThroughTheme,
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
modal.handleInput("\x1b");
|
|
291
|
+
assert.ok(closed, "expected onClose on escape");
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test("handleInput calls onClose on Ctrl+C", () => {
|
|
295
|
+
let closed = false;
|
|
296
|
+
const modal = new SplitPaneInspectorModal(
|
|
297
|
+
{
|
|
298
|
+
getSettings: () => makeMcpSettings(false, false),
|
|
299
|
+
onChange: () => {},
|
|
300
|
+
onClose: () => {
|
|
301
|
+
closed = true;
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
passThroughTheme,
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
modal.handleInput("\x03");
|
|
308
|
+
assert.ok(closed, "expected onClose on ctrl+c");
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test("advanced mode toggles with / input", () => {
|
|
312
|
+
const items = makeMcpSettings(true, false);
|
|
313
|
+
// Add an item with inspectorAdvanced
|
|
314
|
+
const modal = makeModal(items);
|
|
315
|
+
|
|
316
|
+
// Default: advanced not shown
|
|
317
|
+
const linesBefore = modal.render(120);
|
|
318
|
+
const beforeJoined = linesBefore.join(" ");
|
|
319
|
+
assert.equal(beforeJoined.includes("Advanced:"), false, "advanced not shown initially");
|
|
320
|
+
|
|
321
|
+
// Toggle advanced on
|
|
322
|
+
modal.handleInput("/");
|
|
323
|
+
const linesAfterOn = modal.render(120);
|
|
324
|
+
const afterJoined = linesAfterOn.join(" ");
|
|
325
|
+
assert.ok(beforeJoined !== afterJoined, "rendering changed after advanced toggle");
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("footer shows advanced toggle hint", () => {
|
|
329
|
+
const modal = makeModal(makeMcpSettings(true, true));
|
|
330
|
+
const lines = modal.render(100);
|
|
331
|
+
|
|
332
|
+
const footer = lines[lines.length - 1] ?? "";
|
|
333
|
+
assert.ok(footer.includes("/") || footer.includes("advanced"), "footer should reference advanced toggle");
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test("empty settings list renders gracefully", () => {
|
|
337
|
+
const modal = new SplitPaneInspectorModal(
|
|
338
|
+
{
|
|
339
|
+
getSettings: () => [],
|
|
340
|
+
onChange: () => {},
|
|
341
|
+
onClose: () => {},
|
|
342
|
+
},
|
|
343
|
+
passThroughTheme,
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
const lines = modal.render(120);
|
|
347
|
+
assert.ok(lines.length >= 4, "should render even with empty settings");
|
|
348
|
+
const joined = lines.join(" ");
|
|
349
|
+
assert.ok(joined.includes("No matching") || joined.includes("Settings"), "should show empty state");
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test("inspector panel shows item summary", () => {
|
|
353
|
+
const items = makeMcpSettings(false, false);
|
|
354
|
+
const modal = makeModal(items);
|
|
355
|
+
|
|
356
|
+
const lines = modal.render(120);
|
|
357
|
+
const joined = lines.join(" ");
|
|
358
|
+
|
|
359
|
+
// The first (selected) item 'preset' has summary "Determines the overall verbosity."
|
|
360
|
+
assert.ok(joined.includes("Determines"), "should show preset summary");
|
|
361
|
+
// The path only appears for the currently selected item; 'preset' has no path,
|
|
362
|
+
// but the summary is visible in the inspector pane.
|
|
363
|
+
assert.ok(joined.includes("verbosity"), "should show preset summary detail");
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test("theme application adds styled text via fg()", () => {
|
|
367
|
+
const themeRecorder = {
|
|
368
|
+
fg: (color: string, text: string): string => `[${color}]${text}`,
|
|
369
|
+
bold: (text: string): string => `[bold]${text}`,
|
|
370
|
+
} as Theme;
|
|
371
|
+
|
|
372
|
+
const modal = new SplitPaneInspectorModal(
|
|
373
|
+
{
|
|
374
|
+
getSettings: () => makeMcpSettings(false, false),
|
|
375
|
+
onChange: () => {},
|
|
376
|
+
onClose: () => {},
|
|
377
|
+
},
|
|
378
|
+
themeRecorder,
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
const lines = modal.render(120);
|
|
382
|
+
const joined = lines.join(" ");
|
|
383
|
+
|
|
384
|
+
assert.ok(joined.includes("[accent]"), "should apply accent color");
|
|
385
|
+
assert.ok(joined.includes("[bold]"), "should apply bold in header");
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
test("navigating past last item wraps to first", () => {
|
|
389
|
+
const items = makeMcpSettings(false, false);
|
|
390
|
+
const onChange: Array<{ id: string; value: string }> = [];
|
|
391
|
+
const modal = new SplitPaneInspectorModal(
|
|
392
|
+
{
|
|
393
|
+
getSettings: () => items,
|
|
394
|
+
onChange: (id: string, value: string) => {
|
|
395
|
+
onChange.push({ id, value });
|
|
396
|
+
},
|
|
397
|
+
onClose: () => {},
|
|
398
|
+
},
|
|
399
|
+
passThroughTheme,
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
// Press down items.length times to wrap exactly back to the first item
|
|
403
|
+
for (let i = 0; i < items.length; i++) {
|
|
404
|
+
modal.handleInput("\x1b[B");
|
|
405
|
+
}
|
|
406
|
+
// Space should cycle the current (first) item after wrapping
|
|
407
|
+
modal.handleInput(" ");
|
|
408
|
+
|
|
409
|
+
assert.equal(onChange[0]?.id, "preset", "should wrap to first item");
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
test("cycle at last value wraps to first", () => {
|
|
413
|
+
const items: InspectorSettingItem[] = [
|
|
414
|
+
{
|
|
415
|
+
id: "readOutputMode",
|
|
416
|
+
label: "Read tool output",
|
|
417
|
+
currentValue: "preview",
|
|
418
|
+
values: ["hidden", "summary", "preview"],
|
|
419
|
+
inspectorTitle: "Read Tool Output",
|
|
420
|
+
inspectorSummary: ["Test summary."],
|
|
421
|
+
searchTerms: ["read"],
|
|
422
|
+
},
|
|
423
|
+
];
|
|
424
|
+
|
|
425
|
+
// Track changes; update the item's currentValue so each subsequent cycle
|
|
426
|
+
// uses the new value (same way the real controller would).
|
|
427
|
+
const onChange: Array<{ id: string; value: string }> = [];
|
|
428
|
+
const modal = new SplitPaneInspectorModal(
|
|
429
|
+
{
|
|
430
|
+
getSettings: () => items,
|
|
431
|
+
onChange: (id: string, value: string) => {
|
|
432
|
+
onChange.push({ id, value });
|
|
433
|
+
const item = items.find((i) => i.id === id);
|
|
434
|
+
if (item) {
|
|
435
|
+
item.currentValue = value;
|
|
436
|
+
}
|
|
437
|
+
},
|
|
438
|
+
onClose: () => {},
|
|
439
|
+
},
|
|
440
|
+
passThroughTheme,
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
// Cycle three times: preview → hidden → summary → preview
|
|
444
|
+
modal.handleInput(" ");
|
|
445
|
+
modal.handleInput(" ");
|
|
446
|
+
modal.handleInput(" ");
|
|
447
|
+
|
|
448
|
+
assert.equal(onChange.length, 3);
|
|
449
|
+
assert.equal(onChange[2]?.value, "preview", "should wrap back to preview after three cycles");
|
|
450
|
+
});
|