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.
Files changed (76) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +91 -722
  3. package/docs/contract.md +92 -35
  4. package/package.json +13 -1
  5. package/src/api.ts +1 -1
  6. package/.codegraph/.gitignore +0 -5
  7. package/PRD.md +0 -144
  8. package/examples/bash-card.ts +0 -1694
  9. package/gentle-shell-bridge-0.3.0.tgz +0 -0
  10. package/odd/tasks/palette-sections.md +0 -30
  11. package/odd/tasks/plugin-keymap.md +0 -32
  12. package/odd/tasks/plugin-surface-contract.md +0 -55
  13. package/test/api.test.ts +0 -395
  14. package/test/apply-on-move.test.ts +0 -477
  15. package/test/arm.test.ts +0 -220
  16. package/test/bridge-ready.test.ts +0 -309
  17. package/test/bus.test.ts +0 -165
  18. package/test/button-rows.test.ts +0 -409
  19. package/test/capture.test.ts +0 -356
  20. package/test/card-style-rows.test.ts +0 -510
  21. package/test/card-style-transform.test.ts +0 -379
  22. package/test/card-style-wiring.test.ts +0 -258
  23. package/test/card-styles-e2e.test.ts +0 -491
  24. package/test/card-styles.test.ts +0 -324
  25. package/test/card-transcript-path.test.ts +0 -178
  26. package/test/config.test.ts +0 -93
  27. package/test/e2e.test.ts +0 -443
  28. package/test/example-bash-identity.test.ts +0 -372
  29. package/test/example-bash-matrix.test.ts +0 -856
  30. package/test/example-designs.test.ts +0 -734
  31. package/test/example-neon-transform.test.ts +0 -283
  32. package/test/example-plain-style.test.ts +0 -1268
  33. package/test/extension.test.ts +0 -160
  34. package/test/fake-events.ts +0 -58
  35. package/test/fake-host.ts +0 -211
  36. package/test/fake-sidebar.ts +0 -516
  37. package/test/fake-transcript.ts +0 -486
  38. package/test/frame-telemetry.test.ts +0 -417
  39. package/test/hook.test.ts +0 -196
  40. package/test/manifest.test.ts +0 -105
  41. package/test/new-category.test.ts +0 -979
  42. package/test/notices.test.ts +0 -328
  43. package/test/plugin-integration-lines.test.ts +0 -979
  44. package/test/plugin-surface.test.ts +0 -152
  45. package/test/presence.test.ts +0 -459
  46. package/test/rail-surfaces.test.ts +0 -457
  47. package/test/rail-width.test.ts +0 -423
  48. package/test/regression.test.ts +0 -483
  49. package/test/repaint-api.test.ts +0 -200
  50. package/test/reserved-categories.test.ts +0 -361
  51. package/test/row-labels.test.ts +0 -284
  52. package/test/row-names.test.ts +0 -473
  53. package/test/row-origin.test.ts +0 -395
  54. package/test/section-interaction.test.ts +0 -364
  55. package/test/section-panel.test.ts +0 -403
  56. package/test/sections-rows.test.ts +0 -291
  57. package/test/sidebar-backend.test.ts +0 -471
  58. package/test/sidebar-e2e.test.ts +0 -331
  59. package/test/sidebar-hover.test.ts +0 -276
  60. package/test/sidebar-independent.test.ts +0 -349
  61. package/test/sidebar-render.test.ts +0 -281
  62. package/test/sidebar-sections.test.ts +0 -245
  63. package/test/status-card-styles.test.ts +0 -201
  64. package/test/status-section-frame.test.ts +0 -181
  65. package/test/status-sections.test.ts +0 -243
  66. package/test/status.test.ts +0 -209
  67. package/test/store.test.ts +0 -192
  68. package/test/surface-repaint-cost.test.ts +0 -520
  69. package/test/tool-design-chain.test.ts +0 -483
  70. package/test/tool-design-cost.test.ts +0 -473
  71. package/test/tool-design-rows.test.ts +0 -359
  72. package/test/tool-designs.test.ts +0 -745
  73. package/test/transform-pipeline.test.ts +0 -465
  74. package/test/transforms-bus.test.ts +0 -233
  75. package/test/verifier-width-regressions.test.ts +0 -164
  76. package/tsconfig.json +0 -18
@@ -1,457 +0,0 @@
1
- /**
2
- * The rail surface (R1–R5): the card style reaches the sidebar cards through the SAME repaint the
3
- * conversation uses, and a write to bridge-owned state repaints its surface before the action
4
- * returns.
5
- *
6
- * What is real here and what is modelled: the two card fixtures the rail renders are bytes produced
7
- * by gentle-shell's own `lib/shell-card.ts` (see `RAIL_HOST_CARD` in `./fake-sidebar.ts`), and the
8
- * `tui` the bridge walks is the host's own layout shape — root `vstack` → `hstack` →
9
- * `[conversation, rail ScrollView]`, the rail's framed rows being that view's child through the
10
- * layout protocol. The running TUI is not available to this package, so the *host component* is
11
- * modelled and the bridge's walk, wrapper and transform are the real code.
12
- */
13
-
14
- import assert from "node:assert/strict";
15
- import { mkdtempSync, rmSync } from "node:fs";
16
- import { tmpdir } from "node:os";
17
- import { join } from "node:path";
18
- import test from "node:test";
19
-
20
- import { registerCardStyle, registerSection, registerTransform, resetBusForTests, resetCardStylesForTests, resetSectionsForTests, snapshotProviders } from "../src/bus.ts";
21
- import { getCardStyleLastOutcome, resetCardStyleTransformForTests } from "../src/card-style-transform.ts";
22
- import {
23
- disableCardStyleTransform,
24
- installCardStyleTransform,
25
- installRailCardStyleTransform,
26
- resetCardStyleWiringForTests,
27
- } from "../src/card-style-wiring.ts";
28
- import { setCardStyleSelection } from "../src/card-styles.ts";
29
- import {
30
- getRepaintDiagnostics,
31
- requestSurfaceRepaint,
32
- resetRepaintForTests,
33
- setRepaintHost,
34
- } from "../src/repaint.ts";
35
- import { SECTION_ROWS_PROVIDER_ID, installSectionRows } from "../src/sections-rows.ts";
36
- import { disableSidebarBackend, resetSidebarStateForTests } from "../src/sidebar.ts";
37
- import { resetSectionFailuresForTests } from "../src/sidebar-render.ts";
38
- import { collectStatus, formatStatus } from "../src/status.ts";
39
- import { resetStoreStateForTests } from "../src/store.ts";
40
- import type { CustomizeRow, InlineHelpers, RowContext, SidebarSection } from "../src/types.ts";
41
- import {
42
- RAIL_BRIDGE_CARD,
43
- RAIL_HOST_CARD,
44
- type Rail,
45
- createRailTui,
46
- resetSidebar,
47
- } from "./fake-sidebar.ts";
48
- import { createFakeTranscriptTui, stripAnsi } from "./fake-transcript.ts";
49
-
50
- let configHome: string;
51
- let previousConfigHome: string | undefined;
52
-
53
- test.before(() => {
54
- configHome = mkdtempSync(join(tmpdir(), "gsb-rail-surfaces-"));
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
- resetBusForTests();
68
- resetSectionsForTests();
69
- resetCardStylesForTests();
70
- resetCardStyleTransformForTests();
71
- resetCardStyleWiringForTests();
72
- resetRepaintForTests();
73
- resetSidebar();
74
- resetSidebarStateForTests();
75
- resetSectionFailuresForTests();
76
- resetStoreStateForTests();
77
- disableCardStyleTransform();
78
- disableSidebarBackend();
79
- });
80
-
81
- test.afterEach(() => {
82
- disableCardStyleTransform();
83
- disableSidebarBackend();
84
- });
85
-
86
- /** A style whose every row proves which function drew it: no float chrome survives. */
87
- const PLAIN_STYLE = {
88
- id: "loon.plain",
89
- label: "plain",
90
- frame: {
91
- top: (ctx: { title?: string }) => `<${ctx.title ?? ""}>`,
92
- line: (ctx: { text: string }) => `| ${ctx.text}`,
93
- bottom: () => "_",
94
- },
95
- };
96
-
97
- /** The rail as the host frames it: the host's own section card, a gap, then the bridge's. */
98
- const RAIL_FRAMES: readonly string[] = [...RAIL_HOST_CARD, "", ...RAIL_BRIDGE_CARD];
99
-
100
- /** A rail component that hands back ONE array instance every frame, like the host's closure. */
101
- const stableRail = (lines: readonly string[]): Rail => ({
102
- render: () => lines as string[],
103
- });
104
-
105
- const railTui = (lines: readonly string[] = RAIL_FRAMES) =>
106
- createRailTui({ rail: stableRail(lines), contentWidth: 40 });
107
-
108
- const section = (id: string, extra: Partial<SidebarSection> = {}): SidebarSection => ({
109
- id,
110
- title: id.toUpperCase(),
111
- lines: () => [`body ${id}`],
112
- ...extra,
113
- });
114
-
115
- const ctx = (): RowContext => ({ theme: undefined, configHome });
116
-
117
- /** What the host hands a row's action; the bridge's toggles use none of it. */
118
- const inlineHelpers: InlineHelpers = {
119
- input: async () => undefined,
120
- confirm: async () => true,
121
- capture: async () => undefined,
122
- };
123
-
124
- const rowsNow = (): CustomizeRow[] => {
125
- const provider = snapshotProviders().find((entry) => entry.id === SECTION_ROWS_PROVIDER_ID);
126
- assert.ok(provider !== undefined, "the bridge registers its own Sections provider");
127
- return provider.rows(ctx());
128
- };
129
-
130
- const cardBlock = (lines: readonly string[]): string[] => {
131
- const start = lines.findIndex((line) => line.startsWith("card styles: "));
132
- if (start === -1) return [];
133
- const rest = lines.slice(start);
134
- const end = rest.findIndex((line) => line.startsWith("card style note: "));
135
- return end === -1 ? rest : rest.slice(0, end + 1);
136
- };
137
-
138
- // --------------------------------------------------------------------------------------------
139
- // R1 — one card style, every card surface, one shared function
140
- // --------------------------------------------------------------------------------------------
141
-
142
- test("R1: the rail's host section and contributed section are both repainted with the style", () => {
143
- registerCardStyle(PLAIN_STYLE);
144
- setCardStyleSelection(PLAIN_STYLE.id);
145
- const tui = railTui();
146
- const installed = installRailCardStyleTransform(tui);
147
- assert.deepEqual(installed, { installed: true, outcome: "wrapped", component: "rail" });
148
-
149
- const painted = tui.renderRail().map((line) => stripAnsi(line));
150
-
151
- assert.ok(painted.includes("<STATS>"), "the HOST section's card is drawn with the style's top");
152
- assert.ok(painted.includes("<Music>"), "so is the CONTRIBUTED section's card");
153
- assert.deepEqual(
154
- painted.slice(1, 3),
155
- ["| native row 1", "| native row 2"],
156
- "the host section's body rows are redrawn by the style",
157
- );
158
- assert.deepEqual(
159
- painted.slice(6, 8),
160
- ["| track one", "| track two"],
161
- "and the contributed section's body rows too",
162
- );
163
- assert.equal(
164
- painted.some((line) => line.includes("▎")),
165
- false,
166
- "no host float chrome is carried into a repainted card",
167
- );
168
- assert.deepEqual(getCardStyleLastOutcome("rail"), {
169
- kind: "transformed",
170
- styleId: PLAIN_STYLE.id,
171
- cards: 2,
172
- });
173
- });
174
-
175
- test("R1: the rail repaint is the conversation's own function, not a second copy", () => {
176
- registerCardStyle(PLAIN_STYLE);
177
- setCardStyleSelection(PLAIN_STYLE.id);
178
- const conversation = createFakeTranscriptTui({ lines: [...RAIL_FRAMES] });
179
- installCardStyleTransform(conversation);
180
- const tui = railTui();
181
- installRailCardStyleTransform(tui);
182
-
183
- assert.deepEqual(
184
- tui.renderRail(),
185
- conversation.chat.render(40),
186
- "identical input, identical rows: one transform serves both surfaces",
187
- );
188
- });
189
-
190
- test("R1: native restores the host's own frames on the rail, by identity", () => {
191
- registerCardStyle(PLAIN_STYLE);
192
- const tui = railTui();
193
- installRailCardStyleTransform(tui);
194
-
195
- setCardStyleSelection(PLAIN_STYLE.id);
196
- assert.notEqual(tui.renderRail(), RAIL_FRAMES, "a selected style rewrites the frame");
197
-
198
- setCardStyleSelection("native");
199
- assert.equal(
200
- tui.renderRail(),
201
- RAIL_FRAMES,
202
- "native returns the rail's own array instance, untouched",
203
- );
204
- });
205
-
206
- test("R1: a second rail attach wraps once, so a frame is never restyled twice", () => {
207
- registerCardStyle(PLAIN_STYLE);
208
- setCardStyleSelection(PLAIN_STYLE.id);
209
- const tui = railTui();
210
- installRailCardStyleTransform(tui);
211
- const once = tui.renderRail();
212
-
213
- assert.deepEqual(installRailCardStyleTransform(tui), {
214
- installed: true,
215
- outcome: "already-wrapped",
216
- component: "rail",
217
- });
218
- assert.deepEqual(tui.renderRail(), once, "the marker certifies the exact wrapper already installed");
219
- });
220
-
221
- test("R1: a rail mounted after the factory is reached on the next bridge-owned write", () => {
222
- // Pi hands the widget factory a tui before gentle-shell has mounted its fullscreen layout, so the
223
- // first attach can legitimately fail. It must not need a timer: the next write re-attempts once.
224
- registerCardStyle(PLAIN_STYLE);
225
- setCardStyleSelection(PLAIN_STYLE.id);
226
- const tui = railTui();
227
- const late = createRailTui({ rail: stableRail(RAIL_FRAMES), contentWidth: 40 });
228
- const bare = { terminal: tui.terminal, requestRender: () => {} };
229
- setRepaintHost(bare);
230
- assert.deepEqual(installRailCardStyleTransform(bare), {
231
- installed: false,
232
- outcome: "no-layout-root",
233
- });
234
-
235
- // The host mounts its layout; the write that needs the rail repaint is the moment it is reached.
236
- setRepaintHost(late);
237
- setCardStyleSelection("native");
238
- requestSurfaceRepaint({ cause: "card style → native", surfaces: ["conversation", "rail"] });
239
- assert.deepEqual(getRepaintDiagnostics().reach.rail, { reached: false, reason: "no-layout-root" });
240
-
241
- const attached = createRailTui({ rail: stableRail(RAIL_FRAMES), contentWidth: 40 });
242
- setRepaintHost(attached);
243
- installRailCardStyleTransform(attached);
244
- assert.deepEqual(getRepaintDiagnostics().reach.rail, { reached: true });
245
- setCardStyleSelection(PLAIN_STYLE.id);
246
- assert.ok(attached.renderRail().some((line) => stripAnsi(line) === "<STATS>"));
247
- });
248
-
249
- test("R1: a registered transcript transform stays on the conversation, never the rail", () => {
250
- registerCardStyle(PLAIN_STYLE);
251
- setCardStyleSelection(PLAIN_STYLE.id);
252
- registerTransform({ id: "loon.gutter", transform: (lines) => lines.map((line) => `> ${line}`) });
253
- const conversation = createFakeTranscriptTui({ lines: [...RAIL_FRAMES] });
254
- installCardStyleTransform(conversation);
255
- const tui = railTui();
256
- installRailCardStyleTransform(tui);
257
-
258
- assert.ok(
259
- conversation.chat.render(40).every((line) => line.startsWith("> ")),
260
- "the pipeline runs on the conversation",
261
- );
262
- assert.equal(
263
- tui.renderRail().some((line) => line.startsWith("> ")),
264
- false,
265
- "the rail sees the card style only: a plugin's conversation rows never reach another surface",
266
- );
267
- });
268
-
269
- // --------------------------------------------------------------------------------------------
270
- // R2 — the repaint cannot corrupt the rail
271
- // --------------------------------------------------------------------------------------------
272
-
273
- test("R2: a frame that is not a card passes through as the same instance", () => {
274
- registerCardStyle(PLAIN_STYLE);
275
- setCardStyleSelection(PLAIN_STYLE.id);
276
- const prose = ["MUSIC", " track one", "", " track two"];
277
- const tui = railTui(prose);
278
- installRailCardStyleTransform(tui);
279
-
280
- assert.equal(tui.renderRail(), prose, "byte-identical, same array instance");
281
- assert.deepEqual(getCardStyleLastOutcome("rail"), { kind: "pass-through", reason: "no-card" });
282
- });
283
-
284
- test("R2: every repainted row is clipped to the width the host rendered at", () => {
285
- registerCardStyle({
286
- id: "loon.wide",
287
- label: "wide",
288
- frame: {
289
- top: () => "T".repeat(200),
290
- line: () => "L".repeat(200),
291
- bottom: () => "B".repeat(200),
292
- },
293
- });
294
- setCardStyleSelection("loon.wide");
295
- const tui = railTui();
296
- installRailCardStyleTransform(tui);
297
-
298
- for (const row of tui.renderRail().map((line) => stripAnsi(line))) {
299
- assert.ok(row.length <= 40, `${JSON.stringify(row)} fits the rail's content width`);
300
- }
301
- });
302
-
303
- // --------------------------------------------------------------------------------------------
304
- // R3 — a write repaints its surface, synchronously, once, and only when something changed
305
- // --------------------------------------------------------------------------------------------
306
-
307
- test("R3: a visibility toggle repaints the rail before the action returns, exactly once", () => {
308
- registerSection(section("loon.music", { title: "MUSIC" }));
309
- installSectionRows();
310
- const tui = railTui();
311
- setRepaintHost(tui);
312
- assert.equal(tui.cacheRevision(), 0);
313
-
314
- const hidden = rowsNow().find(
315
- (row) => row.choice?.setting === "music" && row.choice.value === "hidden",
316
- );
317
- assert.ok(hidden?.action !== undefined, "the bridge derives the action");
318
- hidden.action(inlineHelpers);
319
-
320
- assert.equal(tui.requestRenderCount(), 1, "one frame was requested, inside the action");
321
- assert.equal(
322
- tui.cacheRevision(),
323
- 1,
324
- "the host's terminal-side cache was invalidated, as invalidateSidebar(tui) does",
325
- );
326
- assert.deepEqual(getRepaintDiagnostics().lastTrigger, {
327
- cause: 'section "loon.music" → hidden',
328
- surfaces: ["rail"],
329
- outcome: "repainted",
330
- });
331
- });
332
-
333
- test("R3: a write that changes nothing doesn't ask for a frame", () => {
334
- registerSection(section("loon.music", { title: "MUSIC" }));
335
- installSectionRows();
336
- const tui = railTui();
337
- setRepaintHost(tui);
338
-
339
- const rows = rowsNow();
340
- const shown = rows.find((row) => row.choice?.setting === "music" && row.choice.value === "shown");
341
- assert.ok(shown?.action !== undefined);
342
- shown.action(inlineHelpers);
343
-
344
- assert.equal(tui.requestRenderCount(), 0, "shown was already shown: no frame, no invalidation");
345
- assert.equal(tui.cacheRevision(), 0);
346
- assert.equal(getRepaintDiagnostics().lastTrigger, undefined);
347
- });
348
-
349
- test("R3: a card-style write repaints both surfaces, synchronously", () => {
350
- registerCardStyle(PLAIN_STYLE);
351
- const tui = railTui();
352
- setRepaintHost(tui);
353
- setCardStyleSelection("native");
354
-
355
- requestSurfaceRepaint({ cause: `card style → ${PLAIN_STYLE.id}`, surfaces: ["conversation", "rail"] });
356
-
357
- assert.equal(tui.requestRenderCount(), 1);
358
- assert.deepEqual(getRepaintDiagnostics().lastTrigger, {
359
- cause: `card style → ${PLAIN_STYLE.id}`,
360
- surfaces: ["conversation", "rail"],
361
- outcome: "repainted",
362
- });
363
- });
364
-
365
- test("R3: nothing polls — the frame is requested in the action, and no timer is scheduled", () => {
366
- registerSection(section("loon.music", { title: "MUSIC" }));
367
- registerCardStyle(PLAIN_STYLE);
368
- installSectionRows();
369
- const tui = railTui();
370
- setRepaintHost(tui);
371
- installRailCardStyleTransform(tui);
372
-
373
- const scheduled = { timeouts: 0, intervals: 0 };
374
- const realSetTimeout = globalThis.setTimeout;
375
- const realSetInterval = globalThis.setInterval;
376
- globalThis.setTimeout = ((...args: Parameters<typeof setTimeout>) => {
377
- scheduled.timeouts += 1;
378
- return realSetTimeout(...args);
379
- }) as typeof setTimeout;
380
- globalThis.setInterval = ((...args: Parameters<typeof setInterval>) => {
381
- scheduled.intervals += 1;
382
- return realSetInterval(...args);
383
- }) as typeof setInterval;
384
- try {
385
- const hidden = rowsNow().find(
386
- (row) => row.choice?.setting === "music" && row.choice.value === "hidden",
387
- );
388
- assert.ok(hidden?.action !== undefined);
389
- hidden.action(inlineHelpers);
390
- tui.renderRail();
391
- } finally {
392
- globalThis.setTimeout = realSetTimeout;
393
- globalThis.setInterval = realSetInterval;
394
- }
395
-
396
- assert.deepEqual(scheduled, { timeouts: 0, intervals: 0 }, "no timer carries the repaint");
397
- assert.equal(tui.requestRenderCount(), 1);
398
- });
399
-
400
- // --------------------------------------------------------------------------------------------
401
- // R4 — the report names the surfaces, the frames and the last trigger
402
- // --------------------------------------------------------------------------------------------
403
-
404
- test("R4: /bridge-status reports each surface's reach and frame, and the last trigger", () => {
405
- registerCardStyle(PLAIN_STYLE);
406
- setCardStyleSelection(PLAIN_STYLE.id);
407
- const conversation = createFakeTranscriptTui({ lines: [...RAIL_FRAMES] });
408
- installCardStyleTransform(conversation);
409
- conversation.chat.render(40);
410
-
411
- const tui = railTui();
412
- setRepaintHost(tui);
413
- installRailCardStyleTransform(tui);
414
- tui.renderRail();
415
- requestSurfaceRepaint({ cause: 'section "loon.music" → hidden', surfaces: ["rail"] });
416
-
417
- const lines = formatStatus(collectStatus());
418
-
419
- assert.deepEqual(cardBlock(lines).slice(-4), [
420
- 'card style surface conversation: reached (chat wrapped), last frame transformed 2 cards',
421
- "card style surface rail: reached (rail wrapped), last frame transformed 2 cards",
422
- "card style wiring: rail wrapped, chat wrapped",
423
- "card style note: a downstream post-processor can strip or override this frame; " +
424
- "the bridge cannot prevent it.",
425
- ]);
426
- assert.ok(
427
- lines.includes('last repaint: rail for section "loon.music" → hidden (repainted)'),
428
- "the last trigger is reported with its cause",
429
- );
430
- });
431
-
432
- test("R4: an unreachable rail is reported, never silently skipped", () => {
433
- registerCardStyle(PLAIN_STYLE);
434
- installRailCardStyleTransform({ terminal: {}, children: [] });
435
- const tui = railTui();
436
-
437
- assert.deepEqual(getRepaintDiagnostics().reach.rail, {
438
- reached: false,
439
- reason: "no-layout-root",
440
- });
441
-
442
- // No host has handed a tui to the bridge yet: the write is reported as unavailable.
443
- resetRepaintForTests();
444
- const trigger = requestSurfaceRepaint({ cause: 'section "loon.music" → hidden', surfaces: ["rail"] });
445
- assert.deepEqual(trigger, {
446
- cause: 'section "loon.music" → hidden',
447
- surfaces: ["rail"],
448
- outcome: "unavailable",
449
- reason: "no tui has been captured, so no surface can be repainted",
450
- });
451
- assert.ok(
452
- formatStatus(collectStatus()).includes(
453
- 'last repaint: rail for section "loon.music" → hidden (unavailable: no tui has been captured, so no surface can be repainted)',
454
- ),
455
- );
456
- assert.equal(tui.requestRenderCount(), 0, "an unreached rail is never asked for a frame");
457
- });