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.
Files changed (75) hide show
  1. package/LICENSE +202 -0
  2. package/docs/contract.md +1 -1
  3. package/package.json +13 -1
  4. package/src/api.ts +1 -1
  5. package/.codegraph/.gitignore +0 -5
  6. package/PRD.md +0 -144
  7. package/examples/bash-card.ts +0 -1694
  8. package/gentle-shell-bridge-0.3.0.tgz +0 -0
  9. package/odd/tasks/palette-sections.md +0 -30
  10. package/odd/tasks/plugin-keymap.md +0 -32
  11. package/odd/tasks/plugin-surface-contract.md +0 -55
  12. package/test/api.test.ts +0 -395
  13. package/test/apply-on-move.test.ts +0 -477
  14. package/test/arm.test.ts +0 -220
  15. package/test/bridge-ready.test.ts +0 -309
  16. package/test/bus.test.ts +0 -165
  17. package/test/button-rows.test.ts +0 -409
  18. package/test/capture.test.ts +0 -356
  19. package/test/card-style-rows.test.ts +0 -510
  20. package/test/card-style-transform.test.ts +0 -379
  21. package/test/card-style-wiring.test.ts +0 -258
  22. package/test/card-styles-e2e.test.ts +0 -491
  23. package/test/card-styles.test.ts +0 -324
  24. package/test/card-transcript-path.test.ts +0 -178
  25. package/test/config.test.ts +0 -93
  26. package/test/e2e.test.ts +0 -443
  27. package/test/example-bash-identity.test.ts +0 -372
  28. package/test/example-bash-matrix.test.ts +0 -856
  29. package/test/example-designs.test.ts +0 -734
  30. package/test/example-neon-transform.test.ts +0 -283
  31. package/test/example-plain-style.test.ts +0 -1268
  32. package/test/extension.test.ts +0 -160
  33. package/test/fake-events.ts +0 -58
  34. package/test/fake-host.ts +0 -211
  35. package/test/fake-sidebar.ts +0 -516
  36. package/test/fake-transcript.ts +0 -486
  37. package/test/frame-telemetry.test.ts +0 -417
  38. package/test/hook.test.ts +0 -196
  39. package/test/manifest.test.ts +0 -105
  40. package/test/new-category.test.ts +0 -979
  41. package/test/notices.test.ts +0 -328
  42. package/test/plugin-integration-lines.test.ts +0 -979
  43. package/test/plugin-surface.test.ts +0 -152
  44. package/test/presence.test.ts +0 -459
  45. package/test/rail-surfaces.test.ts +0 -457
  46. package/test/rail-width.test.ts +0 -423
  47. package/test/regression.test.ts +0 -483
  48. package/test/repaint-api.test.ts +0 -200
  49. package/test/reserved-categories.test.ts +0 -361
  50. package/test/row-labels.test.ts +0 -284
  51. package/test/row-names.test.ts +0 -473
  52. package/test/row-origin.test.ts +0 -395
  53. package/test/section-interaction.test.ts +0 -364
  54. package/test/section-panel.test.ts +0 -403
  55. package/test/sections-rows.test.ts +0 -291
  56. package/test/sidebar-backend.test.ts +0 -471
  57. package/test/sidebar-e2e.test.ts +0 -331
  58. package/test/sidebar-hover.test.ts +0 -276
  59. package/test/sidebar-independent.test.ts +0 -349
  60. package/test/sidebar-render.test.ts +0 -281
  61. package/test/sidebar-sections.test.ts +0 -245
  62. package/test/status-card-styles.test.ts +0 -201
  63. package/test/status-section-frame.test.ts +0 -181
  64. package/test/status-sections.test.ts +0 -243
  65. package/test/status.test.ts +0 -209
  66. package/test/store.test.ts +0 -192
  67. package/test/surface-repaint-cost.test.ts +0 -520
  68. package/test/tool-design-chain.test.ts +0 -483
  69. package/test/tool-design-cost.test.ts +0 -473
  70. package/test/tool-design-rows.test.ts +0 -359
  71. package/test/tool-designs.test.ts +0 -745
  72. package/test/transform-pipeline.test.ts +0 -465
  73. package/test/transforms-bus.test.ts +0 -233
  74. package/test/verifier-width-regressions.test.ts +0 -164
  75. package/tsconfig.json +0 -18
@@ -1,471 +0,0 @@
1
- /**
2
- * Rail interception (S4, S9), proven against the fake host.
3
- *
4
- * The host registers the `footer` part itself (`extensions/gentle-shell.ts:2681`), so our arming
5
- * may run before OR after it. This suite covers both orders, the pass-through of every field the
6
- * host's readers use (`digest` for the layout memo, `spend` for the Stats section), the mouse
7
- * delegation, idempotency across two installs / a rebind / a second package copy, the disable and
8
- * restore path, and the extension point T4 will use to contribute card groups.
9
- */
10
-
11
- import assert from "node:assert/strict";
12
- import test from "node:test";
13
-
14
- import {
15
- SIDEBAR_RAIL_KEY,
16
- disableSidebarBackend,
17
- getSidebarDiagnostics,
18
- installSidebarBackend,
19
- resetSidebarStateForTests,
20
- setRailContributor,
21
- } from "../src/sidebar.ts";
22
- import {
23
- SIDEBAR_STATE_KEY,
24
- cardBody,
25
- cardTitles,
26
- fakeState,
27
- fakeTerminal,
28
- fakeTui,
29
- registerNativeFooter,
30
- registerNativeRail,
31
- renderSidebar,
32
- resetSidebar,
33
- type Rail,
34
- } from "./fake-sidebar.ts";
35
-
36
- interface NativeOptions {
37
- title?: string;
38
- body?: string[];
39
- render?: (width: number) => string[];
40
- cardGroups?: Rail["cardGroups"];
41
- handleMouse?: (event: unknown) => unknown;
42
- }
43
-
44
- interface NativeRail {
45
- rail: Rail;
46
- calls: {
47
- render: number;
48
- cardGroups: number;
49
- digest: number;
50
- spend: number;
51
- handleMouse: number;
52
- open: number;
53
- };
54
- }
55
-
56
- /** The host's own footer part: a real rail shape with every field a reader in the host touches. */
57
- function nativeRail(options: NativeOptions = {}): NativeRail {
58
- const title = options.title ?? "FOOTER";
59
- const body = options.body ?? ["native row 1", "native row 2"];
60
- const calls = {
61
- render: 0,
62
- cardGroups: 0,
63
- digest: 0,
64
- spend: 0,
65
- handleMouse: 0,
66
- open: 0,
67
- };
68
-
69
- const rail: Rail = {
70
- render: (width) => {
71
- calls.render += 1;
72
- return options.render === undefined ? [`▾ ${title}`, ...body] : options.render(width);
73
- },
74
- cardGroups: (width) => {
75
- calls.cardGroups += 1;
76
- return options.cardGroups === undefined
77
- ? { groups: [{ start: 0, end: body.length + 1 }] }
78
- : options.cardGroups(width);
79
- },
80
- digest: () => {
81
- calls.digest += 1;
82
- return "footer:stable";
83
- },
84
- surface: "panel",
85
- summary: () => ({ row: "native summary" }),
86
- open: () => {
87
- calls.open += 1;
88
- },
89
- alert: () => ["needs you"],
90
- spend: () => {
91
- calls.spend += 1;
92
- return { runs: 2, tokens: 3, cost: 4 };
93
- },
94
- invalidate: () => {},
95
- };
96
- if (options.handleMouse !== undefined) {
97
- const handler = options.handleMouse;
98
- rail.handleMouse = (event) => {
99
- calls.handleMouse += 1;
100
- return handler(event);
101
- };
102
- }
103
- return { rail, calls };
104
- }
105
-
106
- const install = () => installSidebarBackend(() => fakeTui());
107
- const footer = () => fakeState().parts.get(SIDEBAR_RAIL_KEY);
108
-
109
- test.beforeEach(() => {
110
- resetSidebar();
111
- resetSidebarStateForTests();
112
- setRailContributor(undefined);
113
- });
114
-
115
- test("a footer the host registers after the bridge armed is captured", () => {
116
- fakeState(); // the host created its rail state at startup, with no footer part yet
117
- const result = install();
118
-
119
- assert.deepEqual(
120
- { installed: result.installed, outcome: result.outcome, rail: result.rail },
121
- { installed: true, outcome: "intercepted", rail: "pending" },
122
- "the map is intercepted even before the part exists",
123
- );
124
- assert.equal(footer(), undefined);
125
-
126
- const native = registerNativeFooter(nativeRail().rail);
127
- const wrapped = footer();
128
-
129
- assert.notEqual(wrapped, native, "the map hands out the wrapper, not the host's raw rail");
130
- assert.equal(wrapped, footer(), "one wrapper: a read never re-wraps");
131
- assert.deepEqual(wrapped?.render(40), native.render(40), "render delegates to the host's rail");
132
- assert.deepEqual(cardTitles(), ["FOOTER"]);
133
- });
134
-
135
- test("an already-registered footer is wrapped on install", () => {
136
- const { rail } = nativeRail();
137
- registerNativeFooter(rail);
138
-
139
- const result = install();
140
-
141
- assert.equal(result.outcome, "intercepted");
142
- assert.equal(result.rail, "wrapped");
143
- assert.notEqual(footer(), rail);
144
- assert.deepEqual(cardTitles(), ["FOOTER"]);
145
- assert.deepEqual(cardBody("FOOTER"), ["native row 1", "native row 2"]);
146
- });
147
-
148
- test("digest, spend, surface, summary, open, alert and invalidate reach their readers", () => {
149
- const { rail, calls } = nativeRail();
150
- registerNativeFooter(rail);
151
- install();
152
-
153
- const wrapped = footer();
154
- assert.ok(wrapped !== undefined);
155
- // The digest's VALUE is the host's own while nothing contributes rows (N14): the layout memo
156
- // compares values (`existing.digest === digest`), never function identity, so the wrapper may keep
157
- // forwarding the host's answer from its own function.
158
- const hostDigest = rail.digest?.();
159
- assert.equal(wrapped.digest?.(), hostDigest, "the host's digest value reaches its reader");
160
- assert.equal(calls.digest, 2, "each read calls the host's own function, once");
161
- assert.equal(wrapped.spend, rail.spend, "spend keeps its identity");
162
- assert.deepEqual(wrapped.spend?.(), { runs: 2, tokens: 3, cost: 4 });
163
- assert.equal(wrapped.surface, "panel");
164
- assert.equal(wrapped.summary, rail.summary);
165
- assert.deepEqual(wrapped.summary?.(), { row: "native summary" });
166
- assert.equal(wrapped.open, rail.open);
167
- wrapped.open?.();
168
- assert.equal(calls.open, 1);
169
- assert.equal(wrapped.alert, rail.alert);
170
- assert.deepEqual(wrapped.alert?.(40), ["needs you"]);
171
-
172
- // The host reads spend exactly like this (`gentle-shell.ts:2652`).
173
- assert.deepEqual(fakeState().parts.get("agents")?.spend?.(), undefined);
174
- registerNativeRail("agents", { ...rail });
175
- install();
176
- assert.deepEqual(fakeState().parts.get(SIDEBAR_RAIL_KEY)?.spend?.(), { runs: 2, tokens: 3, cost: 4 });
177
- });
178
-
179
- test("a throwing host render is rethrown and recorded, never swallowed", () => {
180
- registerNativeFooter(
181
- nativeRail({
182
- render: () => {
183
- throw new Error("host render exploded");
184
- },
185
- }).rail,
186
- );
187
- install();
188
-
189
- assert.throws(() => footer()?.render(40), /host render exploded/);
190
-
191
- const failures = getSidebarDiagnostics().lastFailures;
192
- assert.equal(failures.length, 1);
193
- assert.equal(failures[0].key, SIDEBAR_RAIL_KEY);
194
- assert.equal(failures[0].phase, "render");
195
- assert.equal(failures[0].message, "host render exploded");
196
- });
197
-
198
- test("a throwing host cardGroups is rethrown and recorded, never swallowed", () => {
199
- registerNativeFooter(
200
- nativeRail({
201
- cardGroups: () => {
202
- throw new Error("host groups exploded");
203
- },
204
- }).rail,
205
- );
206
- install();
207
-
208
- assert.throws(() => footer()?.cardGroups?.(40), /host groups exploded/);
209
- const failure = getSidebarDiagnostics().lastFailures.at(-1);
210
- assert.equal(failure?.phase, "cardGroups");
211
- assert.equal(failure?.message, "host groups exploded");
212
- });
213
-
214
- test("a second install on the same tui does not stack a wrapper or duplicate groups", () => {
215
- const { rail, calls } = nativeRail();
216
- registerNativeFooter(rail);
217
- assert.equal(install().outcome, "intercepted");
218
- const first = footer();
219
-
220
- const second = install();
221
-
222
- assert.equal(second.outcome, "already-intercepted");
223
- assert.equal(footer(), first, "the same wrapper object, not a wrapper of a wrapper");
224
-
225
- calls.render = 0;
226
- calls.cardGroups = 0;
227
- const rows = renderSidebar(40);
228
- assert.equal(calls.render, 1, "one frame renders the host's rail exactly once");
229
- assert.equal(calls.cardGroups, 1, "and reads its card groups exactly once");
230
- assert.deepEqual(
231
- rows.filter((row) => row.startsWith("╭")).length,
232
- 1,
233
- "one card, not the same card twice",
234
- );
235
- });
236
-
237
- test("a rebind onto a fresh ui object for the same terminal does not re-wrap", () => {
238
- const { rail } = nativeRail();
239
- registerNativeFooter(rail);
240
- install();
241
- const first = footer();
242
-
243
- const rebound = installSidebarBackend(() => ({ terminal: fakeTerminal() }));
244
-
245
- assert.equal(rebound.outcome, "already-intercepted");
246
- assert.equal(footer(), first);
247
- });
248
-
249
- test("a second copy of the package sees the interception and does not stack", async () => {
250
- const { rail } = nativeRail();
251
- registerNativeFooter(rail);
252
- install();
253
- const first = footer();
254
-
255
- const secondCopySpecifier = "../src/sidebar.ts?copy=2";
256
- const copyTwo: typeof import("../src/sidebar.ts") = await import(secondCopySpecifier);
257
- assert.notEqual(copyTwo, await import("../src/sidebar.ts"));
258
-
259
- const result = copyTwo.installSidebarBackend(() => fakeTui());
260
-
261
- assert.equal(result.outcome, "already-intercepted");
262
- assert.equal(footer(), first, "the marker on the map is process-global, like the slot");
263
- });
264
-
265
- test("disableSidebarBackend restores the host rail and the map's own methods", () => {
266
- const { rail } = nativeRail();
267
- registerNativeFooter(rail);
268
- install();
269
-
270
- const parts = fakeState().parts;
271
- const wrapped = footer();
272
- assert.ok(Object.hasOwn(parts, "set"), "the map is intercepted through an own property");
273
- assert.ok(Object.hasOwn(parts, "get"));
274
- assert.notEqual(footer(), rail);
275
-
276
- assert.equal(disableSidebarBackend(), 1);
277
-
278
- assert.equal(Object.hasOwn(parts, "set"), false, "the prototype's own set/get are in force again");
279
- assert.equal(Object.hasOwn(parts, "get"), false);
280
- assert.equal(fakeState().parts.get(SIDEBAR_RAIL_KEY), rail, "the host's rail is back in the map");
281
-
282
- // A wrapper retained by foreign code must be a plain pass-through now.
283
- assert.deepEqual(wrapped?.render(40), ["▾ FOOTER", "native row 1", "native row 2"]);
284
- });
285
-
286
- test("a foreign wrapper already on the rail is wrapped around, never clobbered", () => {
287
- const { rail } = nativeRail();
288
- let foreignCalls = 0;
289
- const foreign: Rail = {
290
- ...rail,
291
- render: (width) => {
292
- foreignCalls += 1;
293
- return rail.render(width);
294
- },
295
- };
296
- registerNativeFooter(foreign);
297
-
298
- install();
299
-
300
- const wrapped = footer();
301
- assert.ok(wrapped !== undefined);
302
- assert.notEqual(wrapped, foreign, "we wrap the foreign wrapper instead of replacing it");
303
- wrapped.render(40);
304
- assert.equal(foreignCalls, 1, "the foreign wrapper still runs");
305
- assert.equal(wrapped.digest?.(), rail.digest?.(), "and its fields still flow through ours");
306
- });
307
-
308
- test("an unknown tui shape is reported, never thrown", () => {
309
- const noTui = installSidebarBackend(() => undefined);
310
- assert.deepEqual(
311
- { installed: noTui.installed, outcome: noTui.outcome },
312
- { installed: false, outcome: "no-sidebar-state" },
313
- );
314
-
315
- const noTerminal = installSidebarBackend(() => ({}));
316
- assert.equal(noTerminal.outcome, "no-sidebar-state");
317
-
318
- const noState = installSidebarBackend(() => ({ terminal: fakeTerminal(), parts: new Map() }));
319
- assert.equal(noState.outcome, "no-sidebar-state", "a parts map outside the host's slot is not the host's");
320
-
321
- const badState = installSidebarBackend(() => ({
322
- terminal: { [SIDEBAR_STATE_KEY]: { parts: "not a map" } },
323
- }));
324
- assert.equal(badState.outcome, "no-sidebar-state");
325
-
326
- const threw = installSidebarBackend(() => {
327
- throw new Error("no tui yet");
328
- });
329
- assert.equal(threw.installed, false);
330
- assert.equal(threw.outcome, "resolver-threw");
331
- assert.equal(threw.reason, "no tui yet");
332
- });
333
-
334
- test("a resolver that starts returning a tui lets a later install succeed", () => {
335
- let available: { terminal: Record<string | symbol, unknown> } | undefined;
336
- const resolve = () => available;
337
-
338
- assert.equal(installSidebarBackend(resolve).outcome, "no-sidebar-state");
339
-
340
- // The wiring resolved a real tui in between (a widget factory fired, say).
341
- fakeState();
342
- registerNativeFooter(nativeRail().rail);
343
- available = fakeTui();
344
-
345
- const result = installSidebarBackend(resolve);
346
- assert.equal(result.outcome, "intercepted");
347
- assert.notEqual(footer(), undefined);
348
- });
349
-
350
- test("the contributor appends its lines and card groups after the host's own", () => {
351
- registerNativeFooter(nativeRail().rail);
352
- install();
353
- setRailContributor({
354
- render: () => ({ lines: ["▾ EXTRA", "extra row"], groups: [{ start: 0, end: 2 }] }),
355
- });
356
-
357
- const wrapped = footer();
358
- assert.ok(wrapped !== undefined);
359
- assert.deepEqual(wrapped.render(40), [
360
- "▾ FOOTER",
361
- "native row 1",
362
- "native row 2",
363
- "▾ EXTRA",
364
- "extra row",
365
- ]);
366
- assert.deepEqual(wrapped.cardGroups?.(40), {
367
- groups: [
368
- { start: 0, end: 3 },
369
- { start: 3, end: 5 },
370
- ],
371
- });
372
-
373
- assert.deepEqual(cardTitles(), ["FOOTER", "EXTRA"], "the host frames our rows as its own card");
374
- assert.deepEqual(cardBody("EXTRA"), ["extra row"]);
375
- });
376
-
377
- test("a host rail without cardGroups gets one synthesized group covering native and contributed rows", () => {
378
- registerNativeFooter(nativeRail({ cardGroups: undefined, body: ["only row"] }).rail);
379
- const parts = fakeState().parts;
380
- const native = parts.get(SIDEBAR_RAIL_KEY);
381
- assert.ok(native !== undefined);
382
- delete (native as { cardGroups?: unknown }).cardGroups;
383
- install();
384
- setRailContributor({
385
- render: () => ({ lines: ["▾ EXTRA", "extra row"], groups: [{ start: 0, end: 2 }] }),
386
- });
387
-
388
- assert.deepEqual(footer()?.cardGroups?.(40), {
389
- groups: [
390
- { start: 0, end: 2 },
391
- { start: 2, end: 4 },
392
- ],
393
- });
394
- assert.deepEqual(cardTitles(), ["FOOTER", "EXTRA"]);
395
- });
396
-
397
- test("no host frame means no contribution: an empty render stays empty", () => {
398
- registerNativeFooter(
399
- nativeRail({ render: () => [], cardGroups: () => ({ groups: [] }) }).rail,
400
- );
401
- install();
402
- setRailContributor({
403
- render: () => ({ lines: ["▾ EXTRA", "extra row"], groups: [{ start: 0, end: 2 }] }),
404
- });
405
-
406
- assert.deepEqual(footer()?.render(40), [], "the host painted nothing, so neither do we");
407
- assert.deepEqual(footer()?.cardGroups?.(40), { groups: [] });
408
- assert.deepEqual(renderSidebar(40), []);
409
- });
410
-
411
- test("mouse dispatch goes to the host rail first; our targets only after it declines", () => {
412
- const handled = { handled: true, render: true };
413
- registerNativeFooter(
414
- nativeRail({ handleMouse: (event) => (event === "hit" ? handled : undefined) }).rail,
415
- );
416
- install();
417
- const ours: unknown[] = [];
418
- setRailContributor({
419
- render: () => undefined,
420
- handleMouse: (event: unknown) => {
421
- ours.push(event);
422
- return { handled: true, render: true, ours: true };
423
- },
424
- });
425
-
426
- const wrapped = footer();
427
- assert.ok(wrapped?.handleMouse !== undefined);
428
- assert.deepEqual(wrapped.handleMouse("hit"), handled);
429
- assert.deepEqual(ours, [], "the host handled it, so our targets are not consulted");
430
-
431
- assert.deepEqual(wrapped.handleMouse("miss"), { handled: true, render: true, ours: true }, "our target answers");
432
- assert.deepEqual(ours, ["miss"]);
433
- });
434
-
435
- test("our mouse targets work even when the host rail has no mouse handler", () => {
436
- registerNativeFooter(nativeRail().rail); // no handleMouse on the host's rail
437
- install();
438
- setRailContributor({ render: () => undefined, handleMouse: () => ({ ours: true }) });
439
-
440
- assert.deepEqual(footer()?.handleMouse?.("event"), { ours: true });
441
- });
442
-
443
- test("a throwing contributor is recorded and leaves every host row rendering", () => {
444
- registerNativeFooter(nativeRail().rail);
445
- install();
446
- setRailContributor({
447
- render: () => {
448
- throw new Error("section renderer exploded");
449
- },
450
- });
451
-
452
- const wrapped = footer();
453
- assert.deepEqual(wrapped?.render(40), ["▾ FOOTER", "native row 1", "native row 2"]);
454
- assert.deepEqual(cardTitles(), ["FOOTER"], "the host's own card still draws");
455
-
456
- const failure = getSidebarDiagnostics().lastFailures.at(-1);
457
- assert.equal(failure?.message, "section renderer exploded");
458
- assert.equal(failure?.phase, "contribute");
459
- });
460
-
461
- test("the interception only wraps the footer part", () => {
462
- fakeState();
463
- install();
464
- const todo = nativeRail({ title: "TODO" }).rail;
465
- registerNativeRail("todo", todo);
466
- registerNativeFooter(nativeRail().rail);
467
-
468
- assert.equal(fakeState().parts.get("todo"), todo, "another rail keeps its own identity");
469
- assert.notEqual(footer(), undefined);
470
- assert.deepEqual(cardTitles(), ["TODO", "FOOTER"]);
471
- });