gentle-shell-bridge 0.3.0 → 0.3.2
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/LICENSE +202 -0
- package/README.md +91 -722
- package/docs/contract.md +92 -35
- package/package.json +13 -1
- package/src/api.ts +1 -1
- package/.codegraph/.gitignore +0 -5
- package/PRD.md +0 -144
- package/examples/bash-card.ts +0 -1694
- package/gentle-shell-bridge-0.3.0.tgz +0 -0
- package/odd/tasks/palette-sections.md +0 -30
- package/odd/tasks/plugin-keymap.md +0 -32
- package/odd/tasks/plugin-surface-contract.md +0 -55
- package/test/api.test.ts +0 -395
- package/test/apply-on-move.test.ts +0 -477
- package/test/arm.test.ts +0 -220
- package/test/bridge-ready.test.ts +0 -309
- package/test/bus.test.ts +0 -165
- package/test/button-rows.test.ts +0 -409
- package/test/capture.test.ts +0 -356
- package/test/card-style-rows.test.ts +0 -510
- package/test/card-style-transform.test.ts +0 -379
- package/test/card-style-wiring.test.ts +0 -258
- package/test/card-styles-e2e.test.ts +0 -491
- package/test/card-styles.test.ts +0 -324
- package/test/card-transcript-path.test.ts +0 -178
- package/test/config.test.ts +0 -93
- package/test/e2e.test.ts +0 -443
- package/test/example-bash-identity.test.ts +0 -372
- package/test/example-bash-matrix.test.ts +0 -856
- package/test/example-designs.test.ts +0 -734
- package/test/example-neon-transform.test.ts +0 -283
- package/test/example-plain-style.test.ts +0 -1268
- package/test/extension.test.ts +0 -160
- package/test/fake-events.ts +0 -58
- package/test/fake-host.ts +0 -211
- package/test/fake-sidebar.ts +0 -516
- package/test/fake-transcript.ts +0 -486
- package/test/frame-telemetry.test.ts +0 -417
- package/test/hook.test.ts +0 -196
- package/test/manifest.test.ts +0 -105
- package/test/new-category.test.ts +0 -979
- package/test/notices.test.ts +0 -328
- package/test/plugin-integration-lines.test.ts +0 -979
- package/test/plugin-surface.test.ts +0 -152
- package/test/presence.test.ts +0 -459
- package/test/rail-surfaces.test.ts +0 -457
- package/test/rail-width.test.ts +0 -423
- package/test/regression.test.ts +0 -483
- package/test/repaint-api.test.ts +0 -200
- package/test/reserved-categories.test.ts +0 -361
- package/test/row-labels.test.ts +0 -284
- package/test/row-names.test.ts +0 -473
- package/test/row-origin.test.ts +0 -395
- package/test/section-interaction.test.ts +0 -364
- package/test/section-panel.test.ts +0 -403
- package/test/sections-rows.test.ts +0 -291
- package/test/sidebar-backend.test.ts +0 -471
- package/test/sidebar-e2e.test.ts +0 -331
- package/test/sidebar-hover.test.ts +0 -276
- package/test/sidebar-independent.test.ts +0 -349
- package/test/sidebar-render.test.ts +0 -281
- package/test/sidebar-sections.test.ts +0 -245
- package/test/status-card-styles.test.ts +0 -201
- package/test/status-section-frame.test.ts +0 -181
- package/test/status-sections.test.ts +0 -243
- package/test/status.test.ts +0 -209
- package/test/store.test.ts +0 -192
- package/test/surface-repaint-cost.test.ts +0 -520
- package/test/tool-design-chain.test.ts +0 -483
- package/test/tool-design-cost.test.ts +0 -473
- package/test/tool-design-rows.test.ts +0 -359
- package/test/tool-designs.test.ts +0 -745
- package/test/transform-pipeline.test.ts +0 -465
- package/test/transforms-bus.test.ts +0 -233
- package/test/verifier-width-regressions.test.ts +0 -164
- package/tsconfig.json +0 -18
|
@@ -1,359 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The customize rows for tool designs (S6, T9): one line per tool that HAS designs, in the `Cards`
|
|
3
|
-
* category, with the tool's own card as one value and each design as another. The selection is
|
|
4
|
-
* persisted per tool in the bridge's own store, and changing it repaints the cards already drawn
|
|
5
|
-
* through `requestRepaint()` — one differential frame, because the wrapper re-reads the live selection
|
|
6
|
-
* on every render pass (S13/T23: dropping the caches was the freeze).
|
|
7
|
-
*
|
|
8
|
-
* Asserted through the row contract the host receives — `buildRows` — and the store file, so the
|
|
9
|
-
* test reads what the menu reads.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import assert from "node:assert/strict";
|
|
13
|
-
import { mkdtempSync, rmSync } from "node:fs";
|
|
14
|
-
import { tmpdir } from "node:os";
|
|
15
|
-
import { join } from "node:path";
|
|
16
|
-
import test from "node:test";
|
|
17
|
-
|
|
18
|
-
import { snapshotProviders } from "../src/bus.ts";
|
|
19
|
-
import { resetNoticesForTests } from "../src/notices.ts";
|
|
20
|
-
import { resetPluginLinesForTests } from "../src/plugin-lines.ts";
|
|
21
|
-
import { getRepaintDiagnostics, resetRepaintForTests, setRepaintHost } from "../src/repaint.ts";
|
|
22
|
-
import { buildRows, resetRowBuildStateForTests } from "../src/rows.ts";
|
|
23
|
-
import { createStore, resetStoreStateForTests } from "../src/store.ts";
|
|
24
|
-
import {
|
|
25
|
-
TOOL_DESIGN_NATIVE,
|
|
26
|
-
getToolDesignSelection,
|
|
27
|
-
registerToolDesign,
|
|
28
|
-
resetToolDesignsForTests,
|
|
29
|
-
toolNamesWithDesigns,
|
|
30
|
-
} from "../src/tool-designs.ts";
|
|
31
|
-
import {
|
|
32
|
-
TOOL_DESIGN_ROWS_PROVIDER_ID,
|
|
33
|
-
installToolDesignRows,
|
|
34
|
-
resetToolDesignRowStateForTests,
|
|
35
|
-
} from "../src/tool-design-rows.ts";
|
|
36
|
-
import {
|
|
37
|
-
resetToolDesignWiringForTests,
|
|
38
|
-
toolDesignResolver,
|
|
39
|
-
} from "../src/tool-design-wiring.ts";
|
|
40
|
-
import type { CustomizeRow, ToolDesignRegistration } from "../src/types.ts";
|
|
41
|
-
|
|
42
|
-
let configHome: string;
|
|
43
|
-
let previousConfigHome: string | undefined;
|
|
44
|
-
|
|
45
|
-
test.before(() => {
|
|
46
|
-
configHome = mkdtempSync(join(tmpdir(), "gsb-tool-design-rows-"));
|
|
47
|
-
previousConfigHome = process.env.GENTLE_PI_CONFIG_HOME;
|
|
48
|
-
process.env.GENTLE_PI_CONFIG_HOME = configHome;
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test.after(() => {
|
|
52
|
-
if (previousConfigHome === undefined) delete process.env.GENTLE_PI_CONFIG_HOME;
|
|
53
|
-
else process.env.GENTLE_PI_CONFIG_HOME = previousConfigHome;
|
|
54
|
-
rmSync(configHome, { recursive: true, force: true });
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test.beforeEach(() => {
|
|
58
|
-
rmSync(join(configHome, "gentle-shell-bridge"), { recursive: true, force: true });
|
|
59
|
-
resetToolDesignsForTests();
|
|
60
|
-
resetToolDesignRowStateForTests();
|
|
61
|
-
resetToolDesignWiringForTests();
|
|
62
|
-
resetRepaintForTests();
|
|
63
|
-
resetStoreStateForTests();
|
|
64
|
-
resetNoticesForTests();
|
|
65
|
-
resetPluginLinesForTests();
|
|
66
|
-
resetRowBuildStateForTests();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
const bashHeading: ToolDesignRegistration = {
|
|
70
|
-
tool: "bash",
|
|
71
|
-
id: "loon.bash-heading",
|
|
72
|
-
label: "bash heading",
|
|
73
|
-
description: "heads the card with bash instead of the host's $ prompt",
|
|
74
|
-
preview: () => ({ title: "Cards · bash heading", sample: "bash ls -la" }),
|
|
75
|
-
apply: (lines) => [...lines],
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const bashFramed: ToolDesignRegistration = {
|
|
79
|
-
tool: "bash",
|
|
80
|
-
id: "loon.bash-framed",
|
|
81
|
-
label: "bash framed",
|
|
82
|
-
apply: (lines) => [...lines],
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const writeBadge: ToolDesignRegistration = {
|
|
86
|
-
tool: "write",
|
|
87
|
-
id: "loon.write-badge",
|
|
88
|
-
label: "write badge",
|
|
89
|
-
apply: (lines) => [...lines],
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
/** The rows one menu build receives, with the design bus installed. */
|
|
93
|
-
const rowsForBuild = (): CustomizeRow[] => {
|
|
94
|
-
installToolDesignRows();
|
|
95
|
-
return buildRows({ theme: undefined, configHome }).rows;
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
/** The lines the host would draw for one setting, reading `current()` at render time. */
|
|
99
|
-
const toggleLines = (rows: readonly CustomizeRow[]): string[] => {
|
|
100
|
-
const lines: string[] = [];
|
|
101
|
-
let index = 0;
|
|
102
|
-
while (index < rows.length) {
|
|
103
|
-
const setting = rows[index].choice?.setting;
|
|
104
|
-
if (setting === undefined) {
|
|
105
|
-
index += 1;
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
let end = index;
|
|
109
|
-
while (end + 1 < rows.length && rows[end + 1].choice?.setting === setting) end += 1;
|
|
110
|
-
const marks = rows
|
|
111
|
-
.slice(index, end + 1)
|
|
112
|
-
.map((row) => `${row.choice?.current() === true ? "●" : "○"} ${row.choice?.value ?? ""}`);
|
|
113
|
-
lines.push(`${setting} ${marks.join(" ")}`);
|
|
114
|
-
index = end + 1;
|
|
115
|
-
}
|
|
116
|
-
return lines;
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
const recordingTui = (): { calls: string[]; host: Record<string, unknown> } => {
|
|
120
|
-
const calls: string[] = [];
|
|
121
|
-
return {
|
|
122
|
-
calls,
|
|
123
|
-
host: {
|
|
124
|
-
terminal: {},
|
|
125
|
-
invalidate: () => calls.push("invalidate"),
|
|
126
|
-
requestRender: () => calls.push("requestRender"),
|
|
127
|
-
},
|
|
128
|
-
};
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
test("T9: the bridge owns one rows provider, and it is internal plumbing", () => {
|
|
132
|
-
installToolDesignRows();
|
|
133
|
-
assert.deepEqual(
|
|
134
|
-
snapshotProviders().map((provider) => provider.id),
|
|
135
|
-
[TOOL_DESIGN_ROWS_PROVIDER_ID],
|
|
136
|
-
);
|
|
137
|
-
installToolDesignRows();
|
|
138
|
-
assert.equal(snapshotProviders().length, 1, "installing twice replaces the provider in place");
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
test("T9: no design means NO line: a session that never registers one is unchanged", () => {
|
|
142
|
-
assert.deepEqual(rowsForBuild(), []);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
test("T9: one Cards line per tool that has designs, in Cards, values are the tool's card + designs", () => {
|
|
146
|
-
registerToolDesign(bashHeading);
|
|
147
|
-
registerToolDesign(bashFramed);
|
|
148
|
-
registerToolDesign(writeBadge);
|
|
149
|
-
|
|
150
|
-
const rows = rowsForBuild();
|
|
151
|
-
|
|
152
|
-
assert.ok(rows.every((row) => row.category === "Cards"), "every row belongs to Cards");
|
|
153
|
-
const lines = toggleLines(rows);
|
|
154
|
-
assert.equal(lines.length, 2, "one line per tool, never one line per design");
|
|
155
|
-
assert.match(lines[0], /^Card bash /u, "the line is named after the Cards category, then the tool");
|
|
156
|
-
assert.match(lines[0], /● native/u, "the tool's own card is the default selection");
|
|
157
|
-
assert.match(lines[0], /○ bash heading/u);
|
|
158
|
-
assert.match(lines[0], /○ bash framed/u);
|
|
159
|
-
assert.match(lines[1], /^Card write /u);
|
|
160
|
-
assert.match(lines[1], /○ write badge/u);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
test("T9: the selection persists PER TOOL and moves the bullet on the next build", async () => {
|
|
164
|
-
registerToolDesign(bashHeading);
|
|
165
|
-
registerToolDesign(writeBadge);
|
|
166
|
-
|
|
167
|
-
const rows = rowsForBuild();
|
|
168
|
-
const pick = rows.find((row) => row.choice?.value === "bash heading");
|
|
169
|
-
assert.ok(pick?.action !== undefined);
|
|
170
|
-
await pick.action({ input: async () => undefined, confirm: async () => true, capture: async () => undefined });
|
|
171
|
-
|
|
172
|
-
assert.equal(getToolDesignSelection("bash"), "loon.bash-heading");
|
|
173
|
-
assert.equal(getToolDesignSelection("write"), TOOL_DESIGN_NATIVE, "the other tool did not move");
|
|
174
|
-
|
|
175
|
-
const after = toggleLines(rowsForBuild());
|
|
176
|
-
assert.match(after[0], /○ native ● bash heading/u);
|
|
177
|
-
assert.match(after[1], /● native ○ write badge/u);
|
|
178
|
-
|
|
179
|
-
// And the way back to the host's own card.
|
|
180
|
-
const native = rowsForBuild().find(
|
|
181
|
-
(row) => row.choice?.value === TOOL_DESIGN_NATIVE && row.choice?.setting === "Card bash",
|
|
182
|
-
);
|
|
183
|
-
assert.ok(native?.action !== undefined);
|
|
184
|
-
await native.action({ input: async () => undefined, confirm: async () => true, capture: async () => undefined });
|
|
185
|
-
assert.equal(getToolDesignSelection("bash"), TOOL_DESIGN_NATIVE);
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
test("T9: changing the value repaints the drawn cards through requestRepaint()", async () => {
|
|
189
|
-
registerToolDesign(bashHeading);
|
|
190
|
-
const tui = recordingTui();
|
|
191
|
-
setRepaintHost(tui.host);
|
|
192
|
-
|
|
193
|
-
const rows = rowsForBuild();
|
|
194
|
-
const pick = rows.find((row) => row.choice?.value === "bash heading");
|
|
195
|
-
assert.ok(pick?.action !== undefined);
|
|
196
|
-
await pick.action({ input: async () => undefined, confirm: async () => true, capture: async () => undefined });
|
|
197
|
-
|
|
198
|
-
assert.deepEqual(
|
|
199
|
-
tui.calls,
|
|
200
|
-
["requestRender"],
|
|
201
|
-
"one differential frame; no cache is dropped and no forced full redraw is asked for",
|
|
202
|
-
);
|
|
203
|
-
assert.equal(getRepaintDiagnostics().lastTrigger?.outcome, "repainted");
|
|
204
|
-
|
|
205
|
-
// Pressing the value that is already selected costs no frame.
|
|
206
|
-
tui.calls.length = 0;
|
|
207
|
-
await pick.action({ input: async () => undefined, confirm: async () => true, capture: async () => undefined });
|
|
208
|
-
assert.deepEqual(tui.calls, []);
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
test("T9: a design's own preview is the preview its value shows", () => {
|
|
212
|
-
registerToolDesign(bashHeading);
|
|
213
|
-
registerToolDesign(writeBadge);
|
|
214
|
-
|
|
215
|
-
const rows = rowsForBuild();
|
|
216
|
-
const heading = rows.find((row) => row.choice?.value === "bash heading");
|
|
217
|
-
assert.ok(heading?.preview !== undefined);
|
|
218
|
-
assert.deepEqual(heading.preview(), { title: "Cards · bash heading", sample: "bash ls -la" });
|
|
219
|
-
|
|
220
|
-
const badge = rows.find((row) => row.choice?.value === "write badge");
|
|
221
|
-
assert.ok(badge?.preview !== undefined);
|
|
222
|
-
assert.match(badge.preview().title, /write badge/u);
|
|
223
|
-
|
|
224
|
-
const native = rows.find(
|
|
225
|
-
(row) => row.choice?.value === TOOL_DESIGN_NATIVE && row.choice?.setting === "Card bash",
|
|
226
|
-
);
|
|
227
|
-
assert.ok(native?.preview !== undefined);
|
|
228
|
-
assert.match(native.preview().sample, /own card|host/u);
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
test("T9: the line name starts with `Card `, then the tool's readable words, like the host's rows", () => {
|
|
232
|
-
registerToolDesign({ ...writeBadge, tool: "bash", id: "loon.bash-heading" });
|
|
233
|
-
registerToolDesign({ ...writeBadge, tool: "lsp_navigation", id: "loon.lsp" });
|
|
234
|
-
registerToolDesign({ ...writeBadge, tool: "mcp__engram__mem_save", id: "loon.mcp" });
|
|
235
|
-
registerToolDesign({ ...writeBadge, tool: "···", id: "loon.nowords" });
|
|
236
|
-
|
|
237
|
-
const settings = [...new Set(rowsForBuild().map((row) => row.choice?.setting))];
|
|
238
|
-
|
|
239
|
-
assert.deepEqual(settings, [
|
|
240
|
-
"Card bash",
|
|
241
|
-
"Card lsp navigation",
|
|
242
|
-
// The TAIL of a long name is the specific part: `Card mcp engram` would say nothing.
|
|
243
|
-
"Card mem save",
|
|
244
|
-
// A tool name that derives to no words keeps the fallback word.
|
|
245
|
-
"Card tool",
|
|
246
|
-
]);
|
|
247
|
-
for (const setting of settings) {
|
|
248
|
-
assert.ok(setting !== undefined);
|
|
249
|
-
const words = setting.trim().split(/\s+/u);
|
|
250
|
-
assert.ok(words.length >= 1 && words.length <= 3, `${setting} must stay within the word limit`);
|
|
251
|
-
assert.equal(words[0], "Card", `${setting} must start with the Cards category's word`);
|
|
252
|
-
assert.match(setting, /^Card( [a-z0-9]+){1,2}$/u, `${setting} is capitalised like Card style`);
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
test("T9: two tools that clamp to the same name stay distinct and pass the shape rule", () => {
|
|
257
|
-
registerToolDesign({ ...writeBadge, tool: "mcp__alpha__mem_save", id: "loon.alpha" });
|
|
258
|
-
registerToolDesign({ ...writeBadge, tool: "mcp__beta__mem_save", id: "loon.beta" });
|
|
259
|
-
|
|
260
|
-
const settings = [...new Set(rowsForBuild().map((row) => row.choice?.setting))];
|
|
261
|
-
|
|
262
|
-
assert.deepEqual(settings, ["Card mem save", "Card mem 2"], "the second gets the lowest free ordinal");
|
|
263
|
-
for (const setting of settings) {
|
|
264
|
-
assert.ok(setting !== undefined);
|
|
265
|
-
assert.ok(
|
|
266
|
-
setting.trim().split(/\s+/u).length <= 3,
|
|
267
|
-
`${setting} must stay within the row-name word limit`,
|
|
268
|
-
);
|
|
269
|
-
assert.doesNotMatch(setting, /[.:|/\\[\](){}→]/u, `${setting} must carry no structural mark`);
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
test("T9: the persisted value and the stored selection are unchanged by the line's name", async () => {
|
|
274
|
-
registerToolDesign(bashHeading);
|
|
275
|
-
|
|
276
|
-
const pick = rowsForBuild().find((row) => row.choice?.value === "bash heading");
|
|
277
|
-
assert.ok(pick?.action !== undefined);
|
|
278
|
-
assert.equal(pick.choice?.setting, "Card bash", "the line is named after the card, not after the store");
|
|
279
|
-
await pick.action({ input: async () => undefined, confirm: async () => true, capture: async () => undefined });
|
|
280
|
-
|
|
281
|
-
// The VALUE is the design's id and the store is still keyed by the TOOL name: only the visible
|
|
282
|
-
// line name moved.
|
|
283
|
-
assert.equal(getToolDesignSelection("bash"), "loon.bash-heading");
|
|
284
|
-
const stored = createStore("bridge-tool-designs").get<Record<string, unknown>>("selections", {});
|
|
285
|
-
assert.deepEqual(stored, { bash: "loon.bash-heading" });
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
test("T9: a tool name keeps a row name that passes the shape rule, even a long MCP one", () => {
|
|
289
|
-
registerToolDesign({ ...writeBadge, tool: "mcp__engram__mem_save", id: "loon.mcp" });
|
|
290
|
-
registerToolDesign({ ...writeBadge, tool: "mcp__engram__mem_search", id: "loon.mcp2" });
|
|
291
|
-
|
|
292
|
-
installToolDesignRows();
|
|
293
|
-
const result = buildRows({ theme: undefined, configHome });
|
|
294
|
-
const rows = result.rows;
|
|
295
|
-
|
|
296
|
-
assert.deepEqual(result.failures, []);
|
|
297
|
-
const settings = new Set(rows.map((row) => row.choice?.setting));
|
|
298
|
-
assert.equal(settings.size, 2, "two long tool names still produce two distinct lines");
|
|
299
|
-
for (const setting of settings) {
|
|
300
|
-
assert.ok(setting !== undefined);
|
|
301
|
-
assert.ok(
|
|
302
|
-
setting.trim().split(/\s+/u).length <= 3,
|
|
303
|
-
`${setting} must stay within the row-name word limit`,
|
|
304
|
-
);
|
|
305
|
-
assert.doesNotMatch(setting, /[.:|/\\[\](){}→]/u, `${setting} must carry no structural mark`);
|
|
306
|
-
}
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
test("T9: tools are listed in a deterministic order, and only tools with designs appear", () => {
|
|
310
|
-
registerToolDesign(writeBadge);
|
|
311
|
-
registerToolDesign(bashHeading);
|
|
312
|
-
|
|
313
|
-
const rows = rowsForBuild();
|
|
314
|
-
assert.deepEqual(toolNamesWithDesigns(), ["write", "bash"], "registration order");
|
|
315
|
-
assert.deepEqual(
|
|
316
|
-
[...new Set(rows.map((row) => row.choice?.setting))].length,
|
|
317
|
-
2,
|
|
318
|
-
);
|
|
319
|
-
assert.equal(
|
|
320
|
-
rows.some((row) => row.choice?.setting.toLowerCase().includes("edit")),
|
|
321
|
-
false,
|
|
322
|
-
"a tool with no design has no line",
|
|
323
|
-
);
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
test("S2: picking a design in the menu repaints the card ALREADY on screen", async () => {
|
|
327
|
-
// The user's words: the choice has to change "tanto las antiguas como posteriores", not only new
|
|
328
|
-
// cards. Pi resolves the renderers once, when the card is constructed, and re-renders that same
|
|
329
|
-
// component on every frame — so this is the card the menu action must reach.
|
|
330
|
-
const hostCard = ["│ $ ls -la", "│ total 4"];
|
|
331
|
-
const component = { render: (_width: number): string[] => [...hostCard] };
|
|
332
|
-
const renderers = { renderShell: "default", renderCall: () => component };
|
|
333
|
-
|
|
334
|
-
registerToolDesign({
|
|
335
|
-
...bashHeading,
|
|
336
|
-
apply: (lines) => lines.map((line) => line.replace("$ ", "bash ")),
|
|
337
|
-
});
|
|
338
|
-
const resolved = toolDesignResolver("bash", () => renderers);
|
|
339
|
-
assert.ok(resolved !== undefined);
|
|
340
|
-
const card = (resolved.renderCall as () => typeof component)();
|
|
341
|
-
assert.deepEqual(card.render(80), hostCard, "no design selected: the host's own bytes");
|
|
342
|
-
|
|
343
|
-
const pick = rowsForBuild().find((row) => row.choice?.value === "bash heading");
|
|
344
|
-
assert.ok(pick?.action !== undefined);
|
|
345
|
-
await pick.action({ input: async () => undefined, confirm: async () => true, capture: async () => undefined });
|
|
346
|
-
|
|
347
|
-
assert.deepEqual(
|
|
348
|
-
card.render(80),
|
|
349
|
-
["│ bash ls -la", "│ total 4"],
|
|
350
|
-
"the SAME, already-drawn card now shows the design the menu selected",
|
|
351
|
-
);
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
test("PRESERVE: the provider's rows stay empty after the last design is disposed", () => {
|
|
355
|
-
const dispose = registerToolDesign(bashHeading);
|
|
356
|
-
assert.equal(rowsForBuild().length, 2, "native + one design");
|
|
357
|
-
dispose();
|
|
358
|
-
assert.deepEqual(rowsForBuild(), []);
|
|
359
|
-
});
|