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
package/test/fake-transcript.ts
DELETED
|
@@ -1,486 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A fake conversation, and a fake `tui` that reaches it the way Pi does (S8, S4).
|
|
3
|
-
*
|
|
4
|
-
* Two things are modelled here, and nothing else:
|
|
5
|
-
*
|
|
6
|
-
* 1. **The component whose `render(width)` produces the conversation rows.** Its output is real
|
|
7
|
-
* gentle-shell chrome, not an invention: every line below was produced by running
|
|
8
|
-
* `gentle-shell/lib/shell-card.ts` (read-only) — `floatRows` / `cardTop` / `cardLine` /
|
|
9
|
-
* `cardBottom` — with a theme whose `fg` maps the host's roles to SGR codes
|
|
10
|
-
* (`border`→90, `accent`→95, `dim`→2, `muted`→37, `text`→39) and whose `bg` opens a
|
|
11
|
-
* 256-colour background and closes with `\x1b[49m`, the shape `surfaceOpener` requires
|
|
12
|
-
* (`shell-card.ts:288-303`). Width 40 throughout. The grammar these lines encode is documented
|
|
13
|
-
* at the top of `src/card-styles.ts`.
|
|
14
|
-
* 2. **The tree Pi mounts, and the property path from `tui` down to that component.** It is copied
|
|
15
|
-
* from Pi's own construction, not guessed:
|
|
16
|
-
* - `interactive-mode.js:403-407` — the document container's children are
|
|
17
|
-
* `[header, loadedResources, chatContainer]`;
|
|
18
|
-
* - `chat-viewport.js:4-11,22-25` — the transcript is a `primary` `ScrollView` wrapping that
|
|
19
|
-
* document, and the layout root is a `VStack` of `[transcript, dock]`;
|
|
20
|
-
* - `interactive-mode.js:623-629` — mounting adds the seven containers with `addChild` (so
|
|
21
|
-
* `tui.children[0]` is the document, in both renderer modes) and then calls
|
|
22
|
-
* `setLayoutRoot(root)` (fullscreen only);
|
|
23
|
-
* - `pi-tui/dist/layout-node.d.ts:2,36-38` and `components/scroll-view.d.ts:57` — both the
|
|
24
|
-
* stack and the scroll view expose their parts through `LAYOUT_NODE()`.
|
|
25
|
-
*
|
|
26
|
-
* The fake keeps those shapes faithfully: `layoutRoot` is absent in regular mode and before
|
|
27
|
-
* mounting, `children[0]` is always the document when mounted, and the scroll view's
|
|
28
|
-
* `[LAYOUT_NODE]().component` is its child.
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
/** The layout protocol symbol, exactly as pi-tui defines it (`pi-tui/dist/layout-node.js:1`). */
|
|
32
|
-
export const LAYOUT_NODE: symbol = Symbol.for("@earendil-works/pi-tui/layout-node");
|
|
33
|
-
|
|
34
|
-
/** Strips SGR sequences, so a test can assert on what the user sees. */
|
|
35
|
-
export function stripAnsi(line: string): string {
|
|
36
|
-
return line.replace(/\u001b\[[0-9;]*m/g, "");
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* One `float` card, width 40: title `read`, hint `ctrl+o`, one body row `config.ts`.
|
|
41
|
-
* Rows: padding, header, padding, body, closing rule.
|
|
42
|
-
*/
|
|
43
|
-
export const READ_CARD: readonly string[] = [
|
|
44
|
-
" \u001b[48;5;236m\u001b[90m▎\u001b[39m\u001b[90m \u001b[39m\u001b[49m ",
|
|
45
|
-
" \u001b[48;5;236m\u001b[90m▎\u001b[39m\u001b[90m \u001b[39m\u001b[95m✿ read\u001b[39m\u001b[90m \u001b[39m \u001b[2mctrl+o\u001b[39m \u001b[90m \u001b[39m\u001b[49m ",
|
|
46
|
-
" \u001b[48;5;236m\u001b[90m▎\u001b[39m\u001b[90m \u001b[39m\u001b[49m ",
|
|
47
|
-
" \u001b[48;5;236m\u001b[90m▎\u001b[39m \u001b[39mconfig.ts\u001b[39m \u001b[90m \u001b[39m\u001b[49m ",
|
|
48
|
-
" \u001b[48;5;236m\u001b[90m▎\u001b[39m\u001b[90m \u001b[39m\u001b[49m ",
|
|
49
|
-
];
|
|
50
|
-
|
|
51
|
-
// ---------------------------------------------------------------------------------------------
|
|
52
|
-
// The bash card MATRIX (S11/T17): 2 host card styles x 2 card contents, plus the shapes a
|
|
53
|
-
// result can take. Every string below was PRODUCED, not written: a scratch script imported the
|
|
54
|
-
// INSTALLED host's own `lib/shell-card.ts` (`floatRows` / `cardTopRows` / `cardLine` / `cardBottom`
|
|
55
|
-
// / `cardRunningLine`, from
|
|
56
|
-
// `~/.pi/agent/git/github.com/Gentleman-Programming/gentle-shell/lib/shell-card.ts`) and pi's own
|
|
57
|
-
// `@earendil-works/pi-tui` (`Text`, `Container`, `Spacer`) plus
|
|
58
|
-
// `dist/modes/interactive/components/visual-truncate.js` (`VisualLinePreview`), with the arguments
|
|
59
|
-
// the host's real call sites pass:
|
|
60
|
-
//
|
|
61
|
-
// minimal `extensions/quiet-tools.ts:910-914` answers bash with `createQuietToolRenderer("bash")`:
|
|
62
|
-
// `ToolCardTop.render` (`:778-785`) draws the call (`floatRows(cardTopRows(...))`, plus
|
|
63
|
-
// `cardRunningLine` + `cardBottom` while the result is missing) and `ToolCardBody.render`
|
|
64
|
-
// (`:670-700`) draws the result (`body`, `bottom`, `afterHeading: true`). The heading is
|
|
65
|
-
// `formatToolCall`'s title (`:519-527`): the bare command, because the card glyph is `$`
|
|
66
|
-
// (`TOOL_GLYPH`, `:44-52`) — `$ npm test` plus the muted ` (timeout 30s)` and the dim
|
|
67
|
-
// `ctrl+o to expand` hint (`:781`). Collapsed minimal bash results are the empty
|
|
68
|
-
// `Text` (`:800`) so the result is only the closing rule.
|
|
69
|
-
// default the same resolver calls `next()`, so pi's native renderer draws (no gentle-shell card):
|
|
70
|
-
// `core/tools/renderers/bash.js:14-20` is `theme.fg("toolTitle", theme.bold("$ …"))` plus
|
|
71
|
-
// `theme.fg("muted", " (timeout Ns)")` in a `Text("", 0, 0)`; the card is
|
|
72
|
-
// `ToolExecutionComponent`'s `Box(1, 1, bg)` (`modes/interactive/components/
|
|
73
|
-
// tool-execution.js:60`), whose `render` (`pi-tui/components/box.js:96-118`) pads every
|
|
74
|
-
// child row to `width - 2` and adds one blank row above and below. The result is a
|
|
75
|
-
// `Container` of `Spacer(1)` + `VisualLinePreview` (collapsed) or a `Text("\n" + output)`
|
|
76
|
-
// (expanded) — so its FIRST row is blank, and an expanded one starts with a blank row too.
|
|
77
|
-
// `cardStyle()` is irrelevant here: nothing on this path reads it.
|
|
78
|
-
//
|
|
79
|
-
// The theme is the fixture's own fake, applied through the host's `CardTheme` interface; its role
|
|
80
|
-
// map is the one the host's roles get in this file (`border` 90, `success`/`accent`/`toolTitle` 95,
|
|
81
|
-
// `dim` 2, `muted` 37, `text`/`toolOutput` 39) and `bg` opens 256-colour 236 and closes with
|
|
82
|
-
// `\x1b[49m`, the shape `surfaceOpener` requires (`shell-card.ts:288-303`). Colour codes are not
|
|
83
|
-
// what the design reads — the STRUCTURE (chrome, mark, fill, hint, row order) is, and that is what
|
|
84
|
-
// the host code above decides. Widths: 60 for a `float`/`neon` card component, 58 for the native
|
|
85
|
-
// cell (the `Box` spends one column of padding on each side), so every row below is exactly that
|
|
86
|
-
// wide and a design that changes a row without keeping its width is caught by the tests.
|
|
87
|
-
//
|
|
88
|
-
// Reachability, with evidence (see `test/example-bash-matrix.test.ts`):
|
|
89
|
-
// - `minimal` x `float` and `minimal` x `neon` are both reachable: `cardStyle()` picks the chrome
|
|
90
|
-
// inside `floatRows` (`shell-card.ts:314-346`), so the same renderer draws either.
|
|
91
|
-
// - `default` x `float` and `default` x `neon` are reachable and draw the SAME rows: with content
|
|
92
|
-
// default the host's resolver calls `next()` and nothing on pi's path reads `cardStyle()`. The two
|
|
93
|
-
// cells therefore share one fixture; that is the finding, not a shortcut.
|
|
94
|
-
// - A style ADDED through `registerCardStyle` is reachable only with `minimal`. The bridge repaints
|
|
95
|
-
// float frames only (`src/card-style-transform.ts:189-196` requires a ` ▎ ` row, `:320-352`
|
|
96
|
-
// matches the card), and the native cell has no float grammar at all, so a registered style can
|
|
97
|
-
// repaint the minimal card and can never repaint the default one.
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* `minimal` x `float`, the CALL component of a settled card: `floatRows` -> padding row + heading.
|
|
101
|
-
* `quiet-tools.ts:778-785` produces exactly these two rows once the result marked the shared state
|
|
102
|
-
* (`cardAwaitingResult` false, `shell-card.ts:167-181`), which is the state every later frame draws.
|
|
103
|
-
*/
|
|
104
|
-
export const BASH_MINIMAL_FLOAT_CALL: readonly string[] = [
|
|
105
|
-
" \u001b[48;5;236m\u001b[95m▎\u001b[39m\u001b[95m \u001b[39m\u001b[49m ",
|
|
106
|
-
" \u001b[48;5;236m\u001b[95m▎\u001b[39m\u001b[95m \u001b[39m\u001b[95m$ \u001b[95m\u001b[1mnpm test\u001b[22m\u001b[39m\u001b[37m (timeout 30s)\u001b[39m\u001b[39m\u001b[95m \u001b[39m \u001b[2mctrl+o to expand\u001b[39m \u001b[95m \u001b[39m\u001b[49m ",
|
|
107
|
-
];
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* `minimal` x `float`, the CALL component while the command still runs: the same heading, then a
|
|
111
|
-
* padding row, the muted `running…` row and the closing rule (`ToolCardTop.render`, `:782-785`).
|
|
112
|
-
*/
|
|
113
|
-
export const BASH_MINIMAL_FLOAT_CALL_RUNNING: readonly string[] = [
|
|
114
|
-
" \u001b[48;5;236m\u001b[95m▎\u001b[39m\u001b[95m \u001b[39m\u001b[49m ",
|
|
115
|
-
" \u001b[48;5;236m\u001b[95m▎\u001b[39m\u001b[95m \u001b[39m\u001b[95m$ \u001b[95m\u001b[1mnpm test\u001b[22m\u001b[39m\u001b[37m (timeout 30s)\u001b[39m\u001b[39m\u001b[95m \u001b[39m \u001b[2mctrl+o to expand\u001b[39m \u001b[95m \u001b[39m\u001b[49m ",
|
|
116
|
-
" \u001b[48;5;236m\u001b[95m▎\u001b[39m\u001b[95m \u001b[39m\u001b[49m ",
|
|
117
|
-
" \u001b[48;5;236m\u001b[95m▎\u001b[39m \u001b[37mrunning…\u001b[39m \u001b[95m \u001b[39m\u001b[49m ",
|
|
118
|
-
" \u001b[48;5;236m\u001b[95m▎\u001b[39m\u001b[95m \u001b[39m\u001b[49m ",
|
|
119
|
-
];
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* `minimal` x `float`, the RESULT component of a collapsed card with no output: the empty `Text`
|
|
123
|
-
* (`quiet-tools.ts:800`) leaves `ToolCardBody` nothing to draw but the closing rule.
|
|
124
|
-
*/
|
|
125
|
-
export const BASH_MINIMAL_FLOAT_RESULT_EMPTY: readonly string[] = [
|
|
126
|
-
" \u001b[48;5;236m\u001b[95m▎\u001b[39m\u001b[95m \u001b[39m\u001b[49m ",
|
|
127
|
-
];
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* `minimal` x `float`, the RESULT component with two output rows: a separator padding row, the body
|
|
131
|
-
* rows in the muted role, then the closing rule (`ToolCardBody.render` + `floatRows`).
|
|
132
|
-
*/
|
|
133
|
-
export const BASH_MINIMAL_FLOAT_RESULT: readonly string[] = [
|
|
134
|
-
" \u001b[48;5;236m\u001b[95m▎\u001b[39m\u001b[95m \u001b[39m\u001b[49m ",
|
|
135
|
-
" \u001b[48;5;236m\u001b[95m▎\u001b[39m \u001b[37mPASS 42 tests\u001b[39m \u001b[95m \u001b[39m\u001b[49m ",
|
|
136
|
-
" \u001b[48;5;236m\u001b[95m▎\u001b[39m \u001b[37mduration 1.2s\u001b[39m \u001b[95m \u001b[39m\u001b[49m ",
|
|
137
|
-
" \u001b[48;5;236m\u001b[95m▎\u001b[39m\u001b[95m \u001b[39m\u001b[49m ",
|
|
138
|
-
];
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* `minimal` x `float`, the RESULT component of a command that echoed a prompt: its first body row
|
|
142
|
-
* begins with `$ `. From rows alone this row sits exactly where a heading sits, because the call and
|
|
143
|
-
* the result share the float chrome. The design tells them apart by `ctx.part` (S12), so this result
|
|
144
|
-
* passes through byte for byte (`test/example-bash-matrix.test.ts` pins it).
|
|
145
|
-
*/
|
|
146
|
-
export const BASH_MINIMAL_FLOAT_RESULT_PROMPT: readonly string[] = [
|
|
147
|
-
" \u001b[48;5;236m\u001b[95m▎\u001b[39m\u001b[95m \u001b[39m\u001b[49m ",
|
|
148
|
-
" \u001b[48;5;236m\u001b[95m▎\u001b[39m \u001b[37m$ echo hi\u001b[39m \u001b[95m \u001b[39m\u001b[49m ",
|
|
149
|
-
" \u001b[48;5;236m\u001b[95m▎\u001b[39m \u001b[37mhi\u001b[39m \u001b[95m \u001b[39m\u001b[49m ",
|
|
150
|
-
" \u001b[48;5;236m\u001b[95m▎\u001b[39m\u001b[95m \u001b[39m\u001b[49m ",
|
|
151
|
-
];
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* `minimal` x `float`, the CALL component of a settled card whose command is too long for the
|
|
155
|
-
* heading row. Derived from the INSTALLED host, not written by hand: `bashSummary(command, width)`
|
|
156
|
-
* (`quiet-tools.ts:492-501`) clips the command and appends the host's ellipsis, `clipPlain`
|
|
157
|
-
* (`:505-510`) does the cut, and `formatToolCall` (`:519-527`) puts the muted ` (timeout 30s)` after
|
|
158
|
-
* it. The width it is handed is the one `renderCall` computes (`:781`): the card's inner width minus
|
|
159
|
-
* `CARD_TOP_CHROME` (8, `:489`) and the 18 columns of the `ctrl+o to expand` hint — 32 at width 60 —
|
|
160
|
-
* less the timeout's own 14 columns, so the command is clipped to 18 columns. `cardTopRows`
|
|
161
|
-
* (`shell-card.ts:246-271`) then wraps that title and `cardTop` (`:141-164`) leaves only its minimum
|
|
162
|
-
* fill before the hint. That minimum is why the design's fill rule refuses this row.
|
|
163
|
-
*/
|
|
164
|
-
export const BASH_MINIMAL_FLOAT_CALL_LONG: readonly string[] = [
|
|
165
|
-
" \u001b[48;5;236m\u001b[95m▎\u001b[39m\u001b[95m \u001b[39m\u001b[49m ",
|
|
166
|
-
" \u001b[48;5;236m\u001b[95m▎\u001b[39m\u001b[95m \u001b[39m\u001b[95m$ \u001b[95m\u001b[1mnpm run typecheck…\u001b[22m\u001b[39m\u001b[37m (timeout 30s)\u001b[39m\u001b[39m\u001b[95m \u001b[39m \u001b[2mctrl+o to expand\u001b[39m \u001b[95m \u001b[39m\u001b[49m ",
|
|
167
|
-
];
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* `minimal` x `neon`, the same collapsed long command: the outline spends one column fewer on its
|
|
171
|
-
* leading chrome, so `bashSummary` clips two more columns of the command before the ellipsis. Produced
|
|
172
|
-
* by the same host path (the only difference is `floatRows`' chrome, `shell-card.ts:105-108`).
|
|
173
|
-
*/
|
|
174
|
-
export const BASH_MINIMAL_NEON_CALL_LONG: readonly string[] = [
|
|
175
|
-
"\u001b[95m╭\u001b[39m\u001b[95m─ \u001b[39m\u001b[95m$ \u001b[95m\u001b[1mnpm run typecheck -…\u001b[22m\u001b[39m\u001b[37m (timeout 30s)\u001b[39m\u001b[39m\u001b[95m ─\u001b[39m \u001b[2mctrl+o to expand\u001b[39m \u001b[95m╮\u001b[39m",
|
|
176
|
-
];
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* `minimal` x `neon`, the CALL component of a settled card: one row, the outlined top rule
|
|
180
|
-
* (`floatRows` falls back to `OUTLINE_CHROME` when the style is not float, `shell-card.ts:105-108`).
|
|
181
|
-
*/
|
|
182
|
-
export const BASH_MINIMAL_NEON_CALL: readonly string[] = [
|
|
183
|
-
"\u001b[95m╭\u001b[39m\u001b[95m─ \u001b[39m\u001b[95m$ \u001b[95m\u001b[1mnpm test\u001b[22m\u001b[39m\u001b[37m (timeout 30s)\u001b[39m\u001b[39m\u001b[95m ─────────────\u001b[39m \u001b[2mctrl+o to expand\u001b[39m \u001b[95m╮\u001b[39m",
|
|
184
|
-
];
|
|
185
|
-
|
|
186
|
-
/** `minimal` x `neon`, the CALL component while running: top rule, `running…` row, closing rule. */
|
|
187
|
-
export const BASH_MINIMAL_NEON_CALL_RUNNING: readonly string[] = [
|
|
188
|
-
"\u001b[95m╭\u001b[39m\u001b[95m─ \u001b[39m\u001b[95m$ \u001b[95m\u001b[1mnpm test\u001b[22m\u001b[39m\u001b[37m (timeout 30s)\u001b[39m\u001b[39m\u001b[95m ─────────────\u001b[39m \u001b[2mctrl+o to expand\u001b[39m \u001b[95m╮\u001b[39m",
|
|
189
|
-
"\u001b[95m│\u001b[39m \u001b[37mrunning…\u001b[39m \u001b[95m│\u001b[39m",
|
|
190
|
-
"\u001b[95m╰\u001b[39m\u001b[95m──────────────────────────────────────────────────────────╯\u001b[39m",
|
|
191
|
-
];
|
|
192
|
-
|
|
193
|
-
/** `minimal` x `neon`, the RESULT component with no output: only the closing rule. */
|
|
194
|
-
export const BASH_MINIMAL_NEON_RESULT_EMPTY: readonly string[] = [
|
|
195
|
-
"\u001b[95m╰\u001b[39m\u001b[95m──────────────────────────────────────────────────────────╯\u001b[39m",
|
|
196
|
-
];
|
|
197
|
-
|
|
198
|
-
/** `minimal` x `neon`, the RESULT component with two output rows and the closing rule. */
|
|
199
|
-
export const BASH_MINIMAL_NEON_RESULT: readonly string[] = [
|
|
200
|
-
"\u001b[95m│\u001b[39m \u001b[37mPASS 42 tests\u001b[39m \u001b[95m│\u001b[39m",
|
|
201
|
-
"\u001b[95m│\u001b[39m \u001b[37mduration 1.2s\u001b[39m \u001b[95m│\u001b[39m",
|
|
202
|
-
"\u001b[95m╰\u001b[39m\u001b[95m──────────────────────────────────────────────────────────╯\u001b[39m",
|
|
203
|
-
];
|
|
204
|
-
|
|
205
|
-
/** `minimal` x `neon`, the RESULT component of a command that echoed a prompt (`$ echo hi`). */
|
|
206
|
-
export const BASH_MINIMAL_NEON_RESULT_PROMPT: readonly string[] = [
|
|
207
|
-
"\u001b[95m│\u001b[39m \u001b[37m$ echo hi\u001b[39m \u001b[95m│\u001b[39m",
|
|
208
|
-
"\u001b[95m│\u001b[39m \u001b[37mhi\u001b[39m \u001b[95m│\u001b[39m",
|
|
209
|
-
"\u001b[95m╰\u001b[39m\u001b[95m──────────────────────────────────────────────────────────╯\u001b[39m",
|
|
210
|
-
];
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* `default` x `float` AND `default` x `neon` (one fixture: see the reachability note above), the
|
|
214
|
-
* CALL component: pi's native `Text` row, padded to the `Box`'s 58 content columns.
|
|
215
|
-
*/
|
|
216
|
-
export const BASH_DEFAULT_CALL: readonly string[] = [
|
|
217
|
-
"\u001b[95m\u001b[1m$ npm test\u001b[22m\u001b[39m\u001b[37m (timeout 30s)\u001b[39m ",
|
|
218
|
-
];
|
|
219
|
-
|
|
220
|
-
/** The same call with no `timeout` argument, so no muted suffix follows the command. */
|
|
221
|
-
export const BASH_DEFAULT_CALL_NO_TIMEOUT: readonly string[] = [
|
|
222
|
-
"\u001b[95m\u001b[1m$ npm test\u001b[22m\u001b[39m ",
|
|
223
|
-
];
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* The same call with a command long enough to leave no padding after the mark: pi's `Text` wraps it,
|
|
227
|
-
* and the first row is exactly 58 columns wide. A design cannot grow this row without breaking the
|
|
228
|
-
* frame, so it must leave it alone and say so (`test/example-bash-matrix.test.ts`).
|
|
229
|
-
*/
|
|
230
|
-
export const BASH_DEFAULT_CALL_TIGHT: readonly string[] = [
|
|
231
|
-
"\u001b[95m\u001b[1m$ yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\u001b[22m\u001b[39m\u001b[37m (timeout ",
|
|
232
|
-
"\u001b[37m30s)\u001b[39m ",
|
|
233
|
-
];
|
|
234
|
-
|
|
235
|
-
/** `default`, the RESULT component collapsed: a blank `Spacer` row, then the preview rows. */
|
|
236
|
-
export const BASH_DEFAULT_RESULT: readonly string[] = [
|
|
237
|
-
"",
|
|
238
|
-
"\u001b[39mPASS 42 tests\u001b[39m ",
|
|
239
|
-
"\u001b[39mduration 1.2s\u001b[39m ",
|
|
240
|
-
];
|
|
241
|
-
|
|
242
|
-
/** `default`, the RESULT component expanded: `Text("\n" + output)` — its first row is blank too. */
|
|
243
|
-
export const BASH_DEFAULT_RESULT_EXPANDED: readonly string[] = [
|
|
244
|
-
" ",
|
|
245
|
-
"\u001b[39mPASS 42 tests\u001b[39m ",
|
|
246
|
-
"\u001b[39mduration 1.2s\u001b[39m ",
|
|
247
|
-
];
|
|
248
|
-
|
|
249
|
-
/** `default`, the RESULT component of a command whose first output row is `$ echo hi`. */
|
|
250
|
-
export const BASH_DEFAULT_RESULT_PROMPT: readonly string[] = [
|
|
251
|
-
"",
|
|
252
|
-
"\u001b[39m$ echo hi\u001b[39m ",
|
|
253
|
-
"\u001b[39mhi\u001b[39m ",
|
|
254
|
-
];
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* A `read` tool call beside it, drawn by the same host code (glyph `≡`, `TOOL_GLYPH.read`). The
|
|
258
|
-
* heading is `≡ read src/api.ts`; nothing about it is bash, so a bash design must leave it alone.
|
|
259
|
-
*/
|
|
260
|
-
export const HOST_READ_CARD: readonly string[] = [
|
|
261
|
-
" \u001b[48;5;236m\u001b[90m▎\u001b[39m\u001b[90m \u001b[39m\u001b[49m ",
|
|
262
|
-
" \u001b[48;5;236m\u001b[90m▎\u001b[39m\u001b[90m \u001b[39m\u001b[95m≡ read src/api.ts\u001b[39m\u001b[90m \u001b[39m \u001b[2mctrl+o to expand\u001b[39m \u001b[90m \u001b[39m\u001b[49m ",
|
|
263
|
-
" \u001b[48;5;236m\u001b[90m▎\u001b[39m\u001b[90m \u001b[39m\u001b[49m ",
|
|
264
|
-
" \u001b[48;5;236m\u001b[90m▎\u001b[39m \u001b[39mexport function bridgeApi() {\u001b[39m \u001b[90m \u001b[39m\u001b[49m ",
|
|
265
|
-
" \u001b[48;5;236m\u001b[90m▎\u001b[39m\u001b[90m \u001b[39m\u001b[49m ",
|
|
266
|
-
];
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* One `float` card, width 40, with NO hint and NO body: padding, header, padding. Its opener and
|
|
270
|
-
* closer rows are byte-identical to the padded card's, which is why detection must key on
|
|
271
|
-
* position rather than on a row's shape alone.
|
|
272
|
-
*/
|
|
273
|
-
export const BASH_CARD: readonly string[] = [
|
|
274
|
-
" \u001b[48;5;236m\u001b[90m▎\u001b[39m\u001b[90m \u001b[39m\u001b[49m ",
|
|
275
|
-
" \u001b[48;5;236m\u001b[90m▎\u001b[39m\u001b[90m \u001b[39m\u001b[95m✿ bash\u001b[39m\u001b[90m \u001b[39m\u001b[90m \u001b[39m\u001b[49m ",
|
|
276
|
-
" \u001b[48;5;236m\u001b[90m▎\u001b[39m\u001b[90m \u001b[39m\u001b[49m ",
|
|
277
|
-
];
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* One `float` card whose body row was truncated by `cardLine` (`shell-card.ts:172-184`): the
|
|
281
|
-
* `…` arrives with a full reset, and the background is re-armed after it
|
|
282
|
-
* (`paintSurfaceRow`, `:300-303`) — the escape sequence real output carries.
|
|
283
|
-
*/
|
|
284
|
-
export const TRUNCATED_CARD: readonly string[] = [
|
|
285
|
-
" \u001b[48;5;236m\u001b[90m▎\u001b[39m\u001b[90m \u001b[39m\u001b[49m ",
|
|
286
|
-
" \u001b[48;5;236m\u001b[90m▎\u001b[39m\u001b[90m \u001b[39m\u001b[95m✿ bash\u001b[39m\u001b[90m \u001b[39m\u001b[90m \u001b[39m\u001b[49m ",
|
|
287
|
-
" \u001b[48;5;236m\u001b[90m▎\u001b[39m\u001b[90m \u001b[39m\u001b[49m ",
|
|
288
|
-
" \u001b[48;5;236m\u001b[90m▎\u001b[39m \u001b[39mnpm run typecheck exits zero and \u001b[0m\u001b[48;5;236m…\u001b[0m\u001b[48;5;236m \u001b[90m \u001b[39m\u001b[49m ",
|
|
289
|
-
" \u001b[48;5;236m\u001b[90m▎\u001b[39m\u001b[90m \u001b[39m\u001b[49m ",
|
|
290
|
-
];
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* The same card in `neon`: no margin, no background, `╭─ … ╮` / `│ … │` / `╰ … ╯`. Detection must
|
|
294
|
-
* never mistake one style for the other, so both are fixtures.
|
|
295
|
-
*/
|
|
296
|
-
export const NEON_CARD: readonly string[] = [
|
|
297
|
-
"\u001b[90m╭\u001b[39m\u001b[90m─ \u001b[39m\u001b[95m✿ read\u001b[39m\u001b[90m ─────────────────────\u001b[39m \u001b[2mctrl+o\u001b[39m \u001b[90m╮\u001b[39m",
|
|
298
|
-
"\u001b[90m│\u001b[39m \u001b[39mconfig.ts\u001b[39m \u001b[90m│\u001b[39m",
|
|
299
|
-
"\u001b[90m╰\u001b[39m\u001b[90m──────────────────────────────────────╯\u001b[39m",
|
|
300
|
-
];
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* A `float` card with a SUBTITLE and no hint, width 40: the header carries two text runs (title in
|
|
304
|
-
* the title role, `· config.ts` in the muted role) and no dim hint. It pins the honest limit of
|
|
305
|
-
* extraction: a heading whose last run is a subtitle must not be read as a hint.
|
|
306
|
-
*/
|
|
307
|
-
export const SUBTITLE_CARD: readonly string[] = [
|
|
308
|
-
" \u001b[48;5;236m\u001b[90m▎\u001b[39m\u001b[90m \u001b[39m\u001b[49m ",
|
|
309
|
-
" \u001b[48;5;236m\u001b[90m▎\u001b[39m\u001b[90m \u001b[39m\u001b[95m✿ read\u001b[39m \u001b[37m·\u001b[39m \u001b[37mconfig.ts\u001b[39m\u001b[90m \u001b[39m\u001b[90m \u001b[39m\u001b[49m ",
|
|
310
|
-
" \u001b[48;5;236m\u001b[90m▎\u001b[39m\u001b[90m \u001b[39m\u001b[49m ",
|
|
311
|
-
];
|
|
312
|
-
|
|
313
|
-
/** A line the host did not draw as a card, plus a blank one: a repaint must leave both alone. */
|
|
314
|
-
export const PROSE_LINE = "I will read the config first.";
|
|
315
|
-
export const BLANK_LINE = "";
|
|
316
|
-
|
|
317
|
-
/** What the conversation component renders: prose, a card, more prose, a second card. */
|
|
318
|
-
export const CONVERSATION: readonly string[] = [
|
|
319
|
-
PROSE_LINE,
|
|
320
|
-
BLANK_LINE,
|
|
321
|
-
...READ_CARD,
|
|
322
|
-
BLANK_LINE,
|
|
323
|
-
...TRUNCATED_CARD,
|
|
324
|
-
BLANK_LINE,
|
|
325
|
-
...BASH_CARD,
|
|
326
|
-
];
|
|
327
|
-
|
|
328
|
-
/** A component, in Pi's sense: `render(width)` returns one string per row. */
|
|
329
|
-
export interface FakeComponent {
|
|
330
|
-
render(width: number): string[];
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/** The card component: records every width it was asked to render at. */
|
|
334
|
-
export interface FakeConversation extends FakeComponent {
|
|
335
|
-
readonly widths: number[];
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
export function createFakeConversation(lines: readonly string[] = CONVERSATION): FakeConversation {
|
|
339
|
-
const widths: number[] = [];
|
|
340
|
-
return {
|
|
341
|
-
widths,
|
|
342
|
-
render(width: number): string[] {
|
|
343
|
-
widths.push(width);
|
|
344
|
-
return [...lines];
|
|
345
|
-
},
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
/** A component that paints one fixed row, for the header and the loaded-resources slots. */
|
|
350
|
-
function fixedRow(text: string): FakeComponent {
|
|
351
|
-
return { render: () => [text] };
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/** What `LAYOUT_NODE()` returns for a stack / scroll view, narrowed to what a walk reads. */
|
|
355
|
-
export interface FakeLayoutNode {
|
|
356
|
-
type: string;
|
|
357
|
-
entries?: ReadonlyArray<{ component?: unknown }>;
|
|
358
|
-
component?: unknown;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/** Anything exposing the layout protocol, as `ScrollView` and `VStack` both do. */
|
|
362
|
-
export function layoutNodeOf(value: unknown): FakeLayoutNode | undefined {
|
|
363
|
-
if (value === null || typeof value !== "object") return undefined;
|
|
364
|
-
const read = (value as Record<symbol, unknown>)[LAYOUT_NODE];
|
|
365
|
-
if (typeof read !== "function") return undefined;
|
|
366
|
-
const node: unknown = read.call(value);
|
|
367
|
-
if (node === null || typeof node !== "object") return undefined;
|
|
368
|
-
return node as FakeLayoutNode;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/** The transcript `ScrollView`, shaped like `pi-tui`'s: a scrollbar, a child, the layout protocol. */
|
|
372
|
-
export interface FakeScrollView {
|
|
373
|
-
readonly child: unknown;
|
|
374
|
-
scrollbar: "auto" | "hidden" | "always";
|
|
375
|
-
setScrollbar(value: "auto" | "hidden" | "always"): void;
|
|
376
|
-
getContentWidth(width: number): number;
|
|
377
|
-
render(width: number): string[];
|
|
378
|
-
[LAYOUT_NODE](): FakeLayoutNode;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
export interface FakeDocument extends FakeComponent {
|
|
382
|
-
children: FakeComponent[];
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
export interface FakeTui {
|
|
386
|
-
mode: "fullscreen" | "regular";
|
|
387
|
-
terminal: Record<string, unknown>;
|
|
388
|
-
children: FakeComponent[];
|
|
389
|
-
/** Absent in regular mode and before mounting, exactly as on Pi's own renderer. */
|
|
390
|
-
layoutRoot?: { [LAYOUT_NODE](): FakeLayoutNode };
|
|
391
|
-
render(width: number): string[];
|
|
392
|
-
document: FakeDocument;
|
|
393
|
-
transcript?: FakeScrollView;
|
|
394
|
-
chat: FakeConversation;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
export interface FakeTranscriptTuiOptions {
|
|
398
|
-
/** `regular` models `TuiMainScreen`: children only, no layout root. */
|
|
399
|
-
mode?: "fullscreen" | "regular";
|
|
400
|
-
/** `false` models the window before `mountInteractiveTui` runs: nothing mounted at all. */
|
|
401
|
-
mounted?: boolean;
|
|
402
|
-
lines?: readonly string[];
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
/**
|
|
406
|
-
* The fake `tui`. `layoutRoot` (fullscreen, mounted) and `children[0]` (mounted, either mode) both
|
|
407
|
-
* lead to `chat`, so a walk can be written against the documented path and proven here — and the
|
|
408
|
-
* absent shapes are just as reachable.
|
|
409
|
-
*/
|
|
410
|
-
export function createFakeTranscriptTui(options: FakeTranscriptTuiOptions = {}): FakeTui {
|
|
411
|
-
const mode = options.mode ?? "fullscreen";
|
|
412
|
-
const mounted = options.mounted ?? true;
|
|
413
|
-
const chat = createFakeConversation(options.lines ?? CONVERSATION);
|
|
414
|
-
const header = fixedRow("header");
|
|
415
|
-
const loadedResources = fixedRow("resources");
|
|
416
|
-
|
|
417
|
-
// interactive-mode.js:403-407 — the document container's children, in that order.
|
|
418
|
-
const document: FakeDocument = {
|
|
419
|
-
children: [header, loadedResources, chat],
|
|
420
|
-
render(width: number): string[] {
|
|
421
|
-
return this.children.flatMap((child) => child.render(width));
|
|
422
|
-
},
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
// chat-viewport.js:4-11 — a primary ScrollView over the document; its render adds the scrollbar
|
|
426
|
-
// column when `getContentWidth` steals one (pi-tui/components/scroll-view.js:186-190).
|
|
427
|
-
const transcript: FakeScrollView = {
|
|
428
|
-
child: document,
|
|
429
|
-
scrollbar: "auto",
|
|
430
|
-
setScrollbar(value) {
|
|
431
|
-
this.scrollbar = value;
|
|
432
|
-
},
|
|
433
|
-
getContentWidth(width: number): number {
|
|
434
|
-
return this.scrollbar === "hidden" ? width : Math.max(1, width - 1);
|
|
435
|
-
},
|
|
436
|
-
render(width: number): string[] {
|
|
437
|
-
const contentWidth = this.getContentWidth(width);
|
|
438
|
-
const lines = (this.child as FakeComponent).render(contentWidth);
|
|
439
|
-
return contentWidth === width ? lines : lines.map((line) => `${line} `);
|
|
440
|
-
},
|
|
441
|
-
[LAYOUT_NODE](): FakeLayoutNode {
|
|
442
|
-
return { type: "scroll", component: this.child };
|
|
443
|
-
},
|
|
444
|
-
};
|
|
445
|
-
|
|
446
|
-
const dock = {
|
|
447
|
-
[LAYOUT_NODE](): FakeLayoutNode {
|
|
448
|
-
return { type: "vstack", entries: [{ component: fixedRow("editor") }] };
|
|
449
|
-
},
|
|
450
|
-
};
|
|
451
|
-
|
|
452
|
-
const layoutRoot = {
|
|
453
|
-
[LAYOUT_NODE](): FakeLayoutNode {
|
|
454
|
-
return {
|
|
455
|
-
type: "vstack",
|
|
456
|
-
entries: [{ component: transcript }, { component: dock }],
|
|
457
|
-
};
|
|
458
|
-
},
|
|
459
|
-
};
|
|
460
|
-
|
|
461
|
-
const tui: FakeTui = {
|
|
462
|
-
mode,
|
|
463
|
-
terminal: {},
|
|
464
|
-
// interactive-mode.js:622-625 — the seven containers, document first, in both modes.
|
|
465
|
-
children: mounted
|
|
466
|
-
? [document, fixedRow("pending"), fixedRow("status"), fixedRow("above"), fixedRow("editor"), fixedRow("below"), fixedRow("footer")]
|
|
467
|
-
: [],
|
|
468
|
-
render(width: number): string[] {
|
|
469
|
-
// TuiAltScreen renders the layout root and ignores `children` (tui-alt-screen.js:145-151).
|
|
470
|
-
if (this.layoutRoot !== undefined) {
|
|
471
|
-
return (this.layoutRoot[LAYOUT_NODE]().entries ?? []).flatMap((entry) =>
|
|
472
|
-
(entry.component as FakeComponent | undefined)?.render(width) ?? [],
|
|
473
|
-
);
|
|
474
|
-
}
|
|
475
|
-
return this.children.flatMap((child) => child.render(width));
|
|
476
|
-
},
|
|
477
|
-
document,
|
|
478
|
-
chat,
|
|
479
|
-
};
|
|
480
|
-
// Only the fullscreen renderer has a layout root, and only once mounted (interactive-mode.js:626).
|
|
481
|
-
if (mounted && mode === "fullscreen") {
|
|
482
|
-
tui.layoutRoot = layoutRoot;
|
|
483
|
-
tui.transcript = transcript;
|
|
484
|
-
}
|
|
485
|
-
return tui;
|
|
486
|
-
}
|