gentle-shell-bridge 0.3.0 → 0.3.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/LICENSE +202 -0
- package/docs/contract.md +1 -1
- 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,283 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* T10: the worked example's bash DESIGN inside the host's `neon` outline (S1, S5).
|
|
3
|
-
*
|
|
4
|
-
* The user's live frame: the host's own `Card style` line is on `neon`, and the bash card must come
|
|
5
|
-
* back with `bash` where it printed `$` — inside the SAME outline. A design never draws chrome: the
|
|
6
|
-
* host's frame is already there when it is handed the rows, so `neon` (and `float`) survive by
|
|
7
|
-
* construction, and the design only changes the mark and re-balances the rule's fill to keep the row
|
|
8
|
-
* exactly as wide as the host made it.
|
|
9
|
-
*
|
|
10
|
-
* The frames are host-derived fixtures (`test/fake-transcript.ts`: `HOST_BASH_NEON_*`, built from
|
|
11
|
-
* `lib/shell-card.ts` — `cardTop`/`cardLine`/`cardBottom` at width 60), split the way Pi builds a
|
|
12
|
-
* card: the top rule is the call component, the body and closing rule the result component.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import assert from "node:assert/strict";
|
|
16
|
-
import { mkdtempSync, rmSync } from "node:fs";
|
|
17
|
-
import { tmpdir } from "node:os";
|
|
18
|
-
import { join } from "node:path";
|
|
19
|
-
import test from "node:test";
|
|
20
|
-
|
|
21
|
-
import { publishBridgeApi, resetBridgeApiForTests } from "../src/api.ts";
|
|
22
|
-
import {
|
|
23
|
-
resetBusForTests,
|
|
24
|
-
resetCardStylesForTests,
|
|
25
|
-
resetSectionsForTests,
|
|
26
|
-
} from "../src/bus.ts";
|
|
27
|
-
import { resetCardStyleTransformForTests } from "../src/card-style-transform.ts";
|
|
28
|
-
import { disableCardStyleTransform, resetCardStyleWiringForTests } from "../src/card-style-wiring.ts";
|
|
29
|
-
import { disableCaptureBackend, resetCaptureStateForTests } from "../src/capture.ts";
|
|
30
|
-
import gentleShellBridge, { type BridgeExtensionApi } from "../src/extension.ts";
|
|
31
|
-
import { resetHookStateForTests } from "../src/hook.ts";
|
|
32
|
-
import { resetNoticesForTests } from "../src/notices.ts";
|
|
33
|
-
import { resetPluginLinesForTests } from "../src/plugin-lines.ts";
|
|
34
|
-
import { resetRepaintForTests } from "../src/repaint.ts";
|
|
35
|
-
import { buildRows, resetRowBuildStateForTests } from "../src/rows.ts";
|
|
36
|
-
import { disableSidebarBackend, resetSidebarStateForTests } from "../src/sidebar.ts";
|
|
37
|
-
import { resetSectionFailuresForTests } from "../src/sidebar-render.ts";
|
|
38
|
-
import { resetStoreStateForTests } from "../src/store.ts";
|
|
39
|
-
import { resetToolDesignsForTests } from "../src/tool-designs.ts";
|
|
40
|
-
import { resetToolDesignRowStateForTests } from "../src/tool-design-rows.ts";
|
|
41
|
-
import { resetToolDesignWiringForTests } from "../src/tool-design-wiring.ts";
|
|
42
|
-
import type { CustomizeRow, InlineHelpers } from "../src/types.ts";
|
|
43
|
-
import { createFakeHost } from "./fake-host.ts";
|
|
44
|
-
import {
|
|
45
|
-
BASH_MINIMAL_NEON_CALL,
|
|
46
|
-
BASH_MINIMAL_NEON_RESULT,
|
|
47
|
-
stripAnsi,
|
|
48
|
-
} from "./fake-transcript.ts";
|
|
49
|
-
|
|
50
|
-
let configHome: string;
|
|
51
|
-
let previousConfigHome: string | undefined;
|
|
52
|
-
|
|
53
|
-
test.before(() => {
|
|
54
|
-
configHome = mkdtempSync(join(tmpdir(), "gsb-example-neon-"));
|
|
55
|
-
previousConfigHome = process.env.GENTLE_PI_CONFIG_HOME;
|
|
56
|
-
process.env.GENTLE_PI_CONFIG_HOME = configHome;
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test.after(() => {
|
|
60
|
-
if (previousConfigHome === undefined) delete process.env.GENTLE_PI_CONFIG_HOME;
|
|
61
|
-
else process.env.GENTLE_PI_CONFIG_HOME = previousConfigHome;
|
|
62
|
-
rmSync(configHome, { recursive: true, force: true });
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test.beforeEach(() => {
|
|
66
|
-
rmSync(join(configHome, "gentle-shell-bridge"), { recursive: true, force: true });
|
|
67
|
-
resetBridgeApiForTests();
|
|
68
|
-
resetBusForTests();
|
|
69
|
-
resetSectionsForTests();
|
|
70
|
-
resetCardStylesForTests();
|
|
71
|
-
resetCardStyleTransformForTests();
|
|
72
|
-
resetCardStyleWiringForTests();
|
|
73
|
-
resetCaptureStateForTests();
|
|
74
|
-
resetHookStateForTests();
|
|
75
|
-
resetRowBuildStateForTests();
|
|
76
|
-
resetSidebarStateForTests();
|
|
77
|
-
resetSectionFailuresForTests();
|
|
78
|
-
resetNoticesForTests();
|
|
79
|
-
resetPluginLinesForTests();
|
|
80
|
-
resetRepaintForTests();
|
|
81
|
-
resetStoreStateForTests();
|
|
82
|
-
resetToolDesignsForTests();
|
|
83
|
-
resetToolDesignRowStateForTests();
|
|
84
|
-
resetToolDesignWiringForTests();
|
|
85
|
-
disableCaptureBackend();
|
|
86
|
-
disableSidebarBackend();
|
|
87
|
-
disableCardStyleTransform();
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
type SessionHandler = (event: unknown, ctx: { hasUI?: boolean; ui?: unknown }) => void;
|
|
91
|
-
|
|
92
|
-
interface FakePi {
|
|
93
|
-
on?: (event: string, handler: SessionHandler) => void;
|
|
94
|
-
registerCommand?: () => void;
|
|
95
|
-
registerToolRenderer?: (resolver: unknown) => void;
|
|
96
|
-
/** Present because the bridge checks the REQUIRED host package at runtime (S9/T12). */
|
|
97
|
-
getCommands?: () => Array<{ name: string }>;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
type ExampleInit = (pi: FakePi) => () => void;
|
|
101
|
-
|
|
102
|
-
const loadExample = async (tag: string): Promise<ExampleInit> => {
|
|
103
|
-
const specifier = new URL(`../examples/bash-card.ts?${tag}`, import.meta.url).href;
|
|
104
|
-
return ((await import(specifier)) as { default: ExampleInit }).default;
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
const inlineHelpers: InlineHelpers = {
|
|
108
|
-
input: async () => undefined,
|
|
109
|
-
confirm: async () => true,
|
|
110
|
-
capture: async () => undefined,
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
interface FakeComponent {
|
|
114
|
-
render(width: number): string[];
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
interface FakeRenderers {
|
|
118
|
-
renderShell?: unknown;
|
|
119
|
-
renderCall?: unknown;
|
|
120
|
-
renderResult?: unknown;
|
|
121
|
-
[member: string]: unknown;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function fakeComponent(lines: readonly string[]): FakeComponent {
|
|
125
|
-
const rows = [...lines];
|
|
126
|
-
return { render: (): string[] => rows };
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function fakeRenderers(call: readonly string[], result: readonly string[]): FakeRenderers {
|
|
130
|
-
// One component instance per renderer, like Pi: the renderer is called once and the SAME object
|
|
131
|
-
// is re-rendered on every frame, which is what makes "the host's own array instance" observable.
|
|
132
|
-
const callComponent = fakeComponent(call);
|
|
133
|
-
const resultComponent = fakeComponent(result);
|
|
134
|
-
return {
|
|
135
|
-
renderShell: "default",
|
|
136
|
-
renderCall: () => callComponent,
|
|
137
|
-
renderResult: () => resultComponent,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const THEME = { name: "fake-theme" };
|
|
142
|
-
|
|
143
|
-
function mount(renderers: FakeRenderers, member: "renderCall" | "renderResult"): FakeComponent {
|
|
144
|
-
const renderer = renderers[member];
|
|
145
|
-
assert.equal(typeof renderer, "function");
|
|
146
|
-
return member === "renderCall"
|
|
147
|
-
? (renderer as (args: unknown, theme: unknown, ctx: unknown) => FakeComponent)(
|
|
148
|
-
{ command: "npm test" },
|
|
149
|
-
THEME,
|
|
150
|
-
{},
|
|
151
|
-
)
|
|
152
|
-
: (renderer as (result: unknown, options: unknown, theme: unknown, ctx: unknown) => FakeComponent)(
|
|
153
|
-
{ content: [] },
|
|
154
|
-
{ expanded: false },
|
|
155
|
-
THEME,
|
|
156
|
-
{},
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
interface World {
|
|
161
|
-
readonly resolver: (tool: string, next: () => FakeRenderers) => FakeRenderers | undefined;
|
|
162
|
-
rows(): CustomizeRow[];
|
|
163
|
-
pick(value: string): Promise<void>;
|
|
164
|
-
dispose(): void;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
async function startWorld(tag: string): Promise<World> {
|
|
168
|
-
publishBridgeApi();
|
|
169
|
-
const host = createFakeHost();
|
|
170
|
-
const resolvers: unknown[] = [];
|
|
171
|
-
const sessionHandlers: SessionHandler[] = [];
|
|
172
|
-
const pi: FakePi = {
|
|
173
|
-
// `gentle-pi` is present, so the bridge's `session_start` arms (S9/T12).
|
|
174
|
-
getCommands: () => [{ name: "gentle:customize" }],
|
|
175
|
-
on(_event, handler) {
|
|
176
|
-
sessionHandlers.push(handler);
|
|
177
|
-
},
|
|
178
|
-
registerCommand() {},
|
|
179
|
-
registerToolRenderer(resolver) {
|
|
180
|
-
resolvers.push(resolver);
|
|
181
|
-
},
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
gentleShellBridge(pi as BridgeExtensionApi);
|
|
185
|
-
const dispose = (await loadExample(tag))(pi);
|
|
186
|
-
for (const handler of sessionHandlers) handler({}, { hasUI: true, ui: host.ui });
|
|
187
|
-
|
|
188
|
-
assert.equal(resolvers.length, 1, "one resolver, the bridge's");
|
|
189
|
-
const resolver = resolvers[0] as World["resolver"];
|
|
190
|
-
const rows = (): CustomizeRow[] => buildRows({ theme: undefined, configHome }).rows;
|
|
191
|
-
return {
|
|
192
|
-
resolver,
|
|
193
|
-
rows,
|
|
194
|
-
pick: async (value) => {
|
|
195
|
-
const row = rows().find(
|
|
196
|
-
(candidate) =>
|
|
197
|
-
(candidate.choice?.setting === "bash" || candidate.choice?.setting === "Card bash") &&
|
|
198
|
-
candidate.choice?.value === value,
|
|
199
|
-
);
|
|
200
|
-
assert.ok(row?.action !== undefined, `the Cards line carries the value ${value}`);
|
|
201
|
-
await row.action(inlineHelpers);
|
|
202
|
-
},
|
|
203
|
-
dispose,
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* The host's neon top rule with `bash` in place of `$`, three rule columns fewer: the host's bytes are
|
|
209
|
-
* `test/fake-transcript.ts`'s `BASH_MINIMAL_NEON_CALL[0]`, and the rule's own `─` fill is what gives
|
|
210
|
-
* the word its three extra columns back.
|
|
211
|
-
*/
|
|
212
|
-
const DESIGNED_NEON_TOP =
|
|
213
|
-
"\u001b[95m╭\u001b[39m\u001b[95m─ \u001b[39m\u001b[95mbash \u001b[95m\u001b[1mnpm test\u001b[22m\u001b[39m" +
|
|
214
|
-
"\u001b[37m (timeout 30s)\u001b[39m\u001b[39m\u001b[95m ──────────\u001b[39m \u001b[2mctrl+o to expand\u001b[39m" +
|
|
215
|
-
" \u001b[95m╮\u001b[39m";
|
|
216
|
-
|
|
217
|
-
test("T10: the neon outline stays neon, and only the mark changes", async () => {
|
|
218
|
-
const world = await startWorld("neon-design");
|
|
219
|
-
const renderers = fakeRenderers(BASH_MINIMAL_NEON_CALL, BASH_MINIMAL_NEON_RESULT);
|
|
220
|
-
const resolved = world.resolver("bash", () => renderers);
|
|
221
|
-
assert.ok(resolved !== undefined);
|
|
222
|
-
const call = mount(resolved, "renderCall");
|
|
223
|
-
const result = mount(resolved, "renderResult");
|
|
224
|
-
|
|
225
|
-
assert.deepEqual(call.render(60), [...BASH_MINIMAL_NEON_CALL], "the host's neon rule, unchanged");
|
|
226
|
-
|
|
227
|
-
await world.pick("bash heading");
|
|
228
|
-
const output = call.render(60);
|
|
229
|
-
|
|
230
|
-
assert.equal(output.length, 1, "the call component is still one rule");
|
|
231
|
-
assert.equal(output[0], DESIGNED_NEON_TOP, "`bash` where the host printed `$`");
|
|
232
|
-
assert.equal(
|
|
233
|
-
stripAnsi(output[0]).length,
|
|
234
|
-
stripAnsi(BASH_MINIMAL_NEON_CALL[0]).length,
|
|
235
|
-
"the rule is exactly as wide as the host made it",
|
|
236
|
-
);
|
|
237
|
-
assert.ok(stripAnsi(output[0]).startsWith("╭─ bash npm test"), "the outline is still the outline");
|
|
238
|
-
assert.ok(stripAnsi(output[0]).endsWith(" ctrl+o to expand ╮"), "and the hint is still the hint");
|
|
239
|
-
|
|
240
|
-
const body = result.render(60);
|
|
241
|
-
assert.deepEqual(body, [...BASH_MINIMAL_NEON_RESULT], "every body row is byte-identical");
|
|
242
|
-
assert.equal(
|
|
243
|
-
body[body.length - 1],
|
|
244
|
-
BASH_MINIMAL_NEON_RESULT[BASH_MINIMAL_NEON_RESULT.length - 1],
|
|
245
|
-
"the closing rule is the host's own",
|
|
246
|
-
);
|
|
247
|
-
|
|
248
|
-
world.dispose();
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
test("T10: the design leaves a body row that carries `$` inside text alone", async () => {
|
|
252
|
-
const world = await startWorld("neon-content-dollar");
|
|
253
|
-
const result = [
|
|
254
|
-
"\u001b[95m│\u001b[39m \u001b[37mexport PATH=$HOME/bin\u001b[39m \u001b[95m│\u001b[39m",
|
|
255
|
-
BASH_MINIMAL_NEON_RESULT[1],
|
|
256
|
-
BASH_MINIMAL_NEON_RESULT[2],
|
|
257
|
-
];
|
|
258
|
-
const renderers = fakeRenderers(BASH_MINIMAL_NEON_CALL, result);
|
|
259
|
-
const resolved = world.resolver("bash", () => renderers);
|
|
260
|
-
assert.ok(resolved !== undefined);
|
|
261
|
-
const mounted = mount(resolved, "renderResult");
|
|
262
|
-
|
|
263
|
-
await world.pick("bash heading");
|
|
264
|
-
|
|
265
|
-
assert.deepEqual(mounted.render(60), result, "output is never rewritten as if it were the mark");
|
|
266
|
-
world.dispose();
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
test("T10: the host's own card restores the neon rule byte for byte", async () => {
|
|
270
|
-
const world = await startWorld("neon-native");
|
|
271
|
-
const renderers = fakeRenderers(BASH_MINIMAL_NEON_CALL, BASH_MINIMAL_NEON_RESULT);
|
|
272
|
-
const resolved = world.resolver("bash", () => renderers);
|
|
273
|
-
assert.ok(resolved !== undefined);
|
|
274
|
-
const call = mount(resolved, "renderCall");
|
|
275
|
-
|
|
276
|
-
await world.pick("bash heading");
|
|
277
|
-
assert.equal(call.render(60)[0], DESIGNED_NEON_TOP);
|
|
278
|
-
|
|
279
|
-
await world.pick("native");
|
|
280
|
-
assert.deepEqual(call.render(60), [...BASH_MINIMAL_NEON_CALL]);
|
|
281
|
-
|
|
282
|
-
world.dispose();
|
|
283
|
-
});
|