@cplieger/web-terminal-ui 7.3.1 → 8.0.0
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/README.md +114 -18
- package/css/30-tabs.css +52 -13
- package/css/31-switcher.css +15 -0
- package/css/32-split.css +176 -0
- package/css/MANIFEST +1 -0
- package/css/MANIFEST.tabbed +1 -0
- package/package.json +13 -12
- package/src/composition.ts +165 -237
- package/src/features/activity-monitor.ts +20 -55
- package/src/features/animations.ts +12 -30
- package/src/features/clipboard.ts +36 -64
- package/src/features/connection-banner.ts +10 -19
- package/src/features/context-menu.ts +70 -145
- package/src/features/dom.ts +44 -54
- package/src/features/menu-position.ts +24 -28
- package/src/features/mobile-toolbar.ts +32 -62
- package/src/features/predictive-echo.ts +15 -49
- package/src/features/scroll-to-bottom.ts +12 -24
- package/src/features/tabs/index.ts +1398 -1806
- package/src/features/tabs/ink-centre.ts +45 -54
- package/src/features/tabs/model.ts +301 -383
- package/src/features/tabs/strip.ts +45 -97
- package/src/features/tabs/switcher.ts +70 -48
- package/src/index.ts +13 -13
- package/src/kernel/a11y.ts +106 -27
- package/src/kernel/attention.ts +157 -0
- package/src/kernel/conn-state.ts +28 -22
- package/src/kernel/fatal.ts +76 -0
- package/src/kernel/feature-host.ts +136 -0
- package/src/kernel/gesture.ts +9 -13
- package/src/kernel/kernel.ts +57 -2139
- package/src/kernel/layout-policy.ts +44 -0
- package/src/kernel/loading-status.ts +30 -88
- package/src/kernel/navigation.ts +11 -17
- package/src/kernel/notify.ts +158 -0
- package/src/kernel/pane.ts +1415 -0
- package/src/kernel/realm.ts +11 -0
- package/src/kernel/regions.ts +8 -11
- package/src/kernel/scrollback.ts +88 -168
- package/src/kernel/selection.ts +25 -0
- package/src/kernel/shell.ts +1434 -0
- package/src/kernel/split-handle.ts +280 -0
- package/src/kernel/status-share.ts +100 -0
- package/src/kernel/types.ts +457 -441
- package/src/predict.ts +144 -170
- package/src/viewport.ts +124 -197
- package/src/features/tabs/attention.ts +0 -250
- package/src/features/tabs/notify.ts +0 -265
package/src/kernel/types.ts
CHANGED
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
// The v3 kernel/feature contract (design section 22.4).
|
|
2
|
-
//
|
|
3
|
-
// A terminal is a small always-present kernel plus opt-in feature modules. This
|
|
4
|
-
// module is the typed spine both sides hang off: the feature interface, the
|
|
5
|
-
// context the kernel hands each feature, the typed event bus payloads, and the
|
|
6
|
-
// layout-region vocabulary. It is fully typed (no `any`, no stringly-typed
|
|
7
|
-
// capability lookup) so a feature's public API is held by reference and a peer
|
|
8
|
-
// reads it through a typed token.
|
|
9
|
-
|
|
10
1
|
import type { LoadingMessages } from "./loading-status.js";
|
|
11
2
|
import type {
|
|
12
3
|
ScreenMessage,
|
|
13
4
|
ModesMessage,
|
|
14
5
|
LineStore,
|
|
6
|
+
StatusStreamCallbacks,
|
|
15
7
|
StoreSnapshot,
|
|
16
8
|
ViewMemory,
|
|
17
9
|
} from "@cplieger/web-terminal-engine";
|
|
@@ -19,8 +11,6 @@ import type {
|
|
|
19
11
|
/** Cancels a subscription or registration. Idempotent by convention. */
|
|
20
12
|
export type Unsubscribe = () => void;
|
|
21
13
|
|
|
22
|
-
// --- Layout regions (section 22.13) ---
|
|
23
|
-
|
|
24
14
|
/** The named layout regions the kernel owns. A feature mounts its chrome into
|
|
25
15
|
* one of these; the region owns position, spacing, stack direction, z-order,
|
|
26
16
|
* and the keyboard-inset lift. */
|
|
@@ -37,8 +27,6 @@ export type RegionName =
|
|
|
37
27
|
* visual order (WCAG 2.4.3). */
|
|
38
28
|
export type RegionSlot = string;
|
|
39
29
|
|
|
40
|
-
// --- Session references ---
|
|
41
|
-
|
|
42
30
|
/** A reference to a session, carried on switch events and onSwitch. */
|
|
43
31
|
export interface SessionRef {
|
|
44
32
|
readonly id: string;
|
|
@@ -55,8 +43,6 @@ export interface SessionView {
|
|
|
55
43
|
highestIndex(): number;
|
|
56
44
|
}
|
|
57
45
|
|
|
58
|
-
// --- Connection state (mirrors the kernel's connection-state machine) ---
|
|
59
|
-
|
|
60
46
|
/** Connection state the kernel owns and broadcasts on `connection:state`. */
|
|
61
47
|
export type ConnState =
|
|
62
48
|
| "open"
|
|
@@ -70,17 +56,14 @@ export type ConnState =
|
|
|
70
56
|
| "ended"
|
|
71
57
|
// The engine refused an explicitly incompatible wire revision. This is a
|
|
72
58
|
// terminal state, not a transient disconnect; page reload/update is required.
|
|
73
|
-
| "incompatible"
|
|
74
|
-
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
// deliberately NOT exposed: features send only through ctx.send / ctx.paste (the
|
|
82
|
-
// sanitizing funnel) and never touch the raw outbox/resume layer or the binary
|
|
83
|
-
// frame format (section 22.9).
|
|
59
|
+
| "incompatible"
|
|
60
|
+
// The pane holds no session (its shown tab left while the terminal stays up):
|
|
61
|
+
// nothing is connecting, so there is nothing to show. Left by the next attach.
|
|
62
|
+
| "idle";
|
|
63
|
+
|
|
64
|
+
// The subset of the engine's renderer and scroll controller features may drive;
|
|
65
|
+
// the kernel assigns the real instances, so drift is caught there. The connection
|
|
66
|
+
// is NOT exposed: features send only through the sanitizing funnel.
|
|
84
67
|
|
|
85
68
|
/** The render methods features may drive. */
|
|
86
69
|
export interface RenderHandle {
|
|
@@ -120,29 +103,234 @@ export interface ScrollHandle {
|
|
|
120
103
|
* memory. The engine's scroll controller owns the container's scrollTop;
|
|
121
104
|
* features go through this seam, never the DOM element (engine >= v3). */
|
|
122
105
|
currentScrollTop(): number;
|
|
123
|
-
/** Restore
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
restoreScrollTop(top: number): void;
|
|
132
|
-
/** Restore an offset AND its follow state together, which is the only way to
|
|
133
|
-
* express "holding at the bottom" (a content shrink clamps a scrolled-up reader
|
|
134
|
-
* there without engaging follow, so position alone cannot say it).
|
|
135
|
-
*
|
|
136
|
-
* NOT the per-view scroll-memory API: this is still a PIXEL offset, with the
|
|
137
|
-
* same defect as `restoreScrollTop` above — a rebuild clamps it and a tab whose
|
|
138
|
-
* content grew while backgrounded lands on a different line. `render.bind(store,
|
|
139
|
-
* { view })` with `render.captureViewMemory()` is what a consumer wants; the
|
|
140
|
-
* engine calls this internally from that path. Declared here because it is part
|
|
141
|
-
* of the engine's scroll surface, not because a consumer should reach for it. */
|
|
106
|
+
/** Restore an offset AND its follow state together, the only way to express
|
|
107
|
+
* "holding at the bottom" (a content shrink clamps a scrolled-up reader there
|
|
108
|
+
* without engaging follow, so position alone cannot say it). NOT the per-view
|
|
109
|
+
* scroll-memory API: this is a PIXEL offset, so a rebuild clamps it and a tab
|
|
110
|
+
* whose content grew while backgrounded lands on a different line;
|
|
111
|
+
* `render.bind(store, { view })` with `render.captureViewMemory()` is what a
|
|
112
|
+
* consumer wants, and the engine calls this from that path. Declared because
|
|
113
|
+
* it is part of the engine's scroll surface. */
|
|
142
114
|
restoreView(view: { top: number; following: boolean }): void;
|
|
143
115
|
}
|
|
144
116
|
|
|
145
|
-
|
|
117
|
+
/** The engine's mode-state readers a feature may consult (never write): the
|
|
118
|
+
* connection writes them from the server's modes frames, and the engine's
|
|
119
|
+
* `ModeState` satisfies this. */
|
|
120
|
+
export interface ModeReaders {
|
|
121
|
+
/** True when the server has DEC 2004 (bracketed paste) enabled. */
|
|
122
|
+
isBracketedPaste(): boolean;
|
|
123
|
+
/** True when the server has DECCKM (application cursor keys) enabled. */
|
|
124
|
+
isApplicationCursor(): boolean;
|
|
125
|
+
/** Active mouse tracking mode (xterm DECSET): 0 = off, 1000 = normal, 1002 = button-event, 1003 = any-event. */
|
|
126
|
+
getMouseMode(): number;
|
|
127
|
+
/** True when the server has DEC 1006 (SGR mouse encoding) enabled. */
|
|
128
|
+
isMouseSGR(): boolean;
|
|
129
|
+
/** True when the server has DEC 1016 (SGR-pixels mouse) enabled: reports carry pixel coordinates. */
|
|
130
|
+
isMousePixels(): boolean;
|
|
131
|
+
/** True when the server has DEC 1004 (focus event reporting) enabled. */
|
|
132
|
+
isFocusReporting(): boolean;
|
|
133
|
+
/** True when the server has DECKPAM (application keypad) enabled. */
|
|
134
|
+
isApplicationKeypad(): boolean;
|
|
135
|
+
/** True when the server has DEC 5 (reverse video / DECSCNM) enabled. */
|
|
136
|
+
isReverseVideo(): boolean;
|
|
137
|
+
/** Kitty keyboard progressive-enhancement flags in effect (bit0 disambiguate,
|
|
138
|
+
* bit1 report-event-types, bit2 report-alternate-keys); 0 means legacy encoding. */
|
|
139
|
+
getKeyboardFlags(): number;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** One of the two display panes a terminal can show. */
|
|
143
|
+
export type PaneSide = "left" | "right";
|
|
144
|
+
|
|
145
|
+
/** A pane kernel as the shell and a shell-scoped feature see it. */
|
|
146
|
+
export interface PaneHandle {
|
|
147
|
+
/** Left for the pane a closed split shows and right for the one it hides; both
|
|
148
|
+
* sides are reassigned at every open and close, so no session moves between
|
|
149
|
+
* kernels for either. */
|
|
150
|
+
readonly side: PaneSide;
|
|
151
|
+
/** The pane root. */
|
|
152
|
+
readonly root: HTMLElement;
|
|
153
|
+
/** This pane's `.term`. */
|
|
154
|
+
surface(): HTMLElement;
|
|
155
|
+
readonly render: RenderHandle;
|
|
156
|
+
readonly scroll: ScrollHandle;
|
|
157
|
+
readonly modes: ModeReaders;
|
|
158
|
+
/** This pane's active session, or none. */
|
|
159
|
+
readonly session: SessionView;
|
|
160
|
+
/** `shown` has an active session; `hidden` is the pane a closed split hides. */
|
|
161
|
+
state(): "shown" | "empty" | "failed" | "hidden";
|
|
162
|
+
/** This pane's regions. */
|
|
163
|
+
region(name: RegionName, slot?: RegionSlot): HTMLElement;
|
|
164
|
+
on<K extends keyof TerminalEvents>(
|
|
165
|
+
event: K,
|
|
166
|
+
cb: (payload: TerminalEvents[K]) => void,
|
|
167
|
+
): Unsubscribe;
|
|
168
|
+
/** Show a session here. */
|
|
169
|
+
notifySwitch(session: SessionRef): void;
|
|
170
|
+
/** Make this pane empty: detach the shown session, forget it on this pane's
|
|
171
|
+
* connection (which closes its socket), clear the screen and go `idle`, so no
|
|
172
|
+
* wake handler reconnects a pane that holds no session. Idempotent. */
|
|
173
|
+
clearActiveSession(): void;
|
|
174
|
+
/** Send this pane's grid size now, once its font metrics are known. The
|
|
175
|
+
* viewport's own settle is not waited for: a handle drag sends the geometry
|
|
176
|
+
* under the pointer, and the engine drops a size equal to the last sent. */
|
|
177
|
+
announceSize(): void;
|
|
178
|
+
focus(): void;
|
|
179
|
+
send(bytes: Uint8Array): void;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** The split view's state. */
|
|
183
|
+
export interface SplitState {
|
|
184
|
+
readonly open: boolean;
|
|
185
|
+
/** Open, but the pane row is under the width two panes need, so one is shown. */
|
|
186
|
+
readonly collapsed: boolean;
|
|
187
|
+
/** The EFFECTIVE left share of the pane row, 0 to 1. */
|
|
188
|
+
readonly ratio: number;
|
|
189
|
+
/** The remembered share the layout record holds. */
|
|
190
|
+
readonly committedRatio: number;
|
|
191
|
+
readonly selected: PaneSide;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Opens, closes and resizes the two-pane split. On a terminal built without the
|
|
195
|
+
* split option `enabled` is false and every mutator returns false. */
|
|
196
|
+
export interface SplitController {
|
|
197
|
+
readonly enabled: boolean;
|
|
198
|
+
state(): SplitState;
|
|
199
|
+
isOpen(): boolean;
|
|
200
|
+
/** Enabled, closed, and the pane row is wide enough for two panes. */
|
|
201
|
+
canOpen(): boolean;
|
|
202
|
+
/** True when the split is now open at a 50/50 share; false when nothing changed. */
|
|
203
|
+
open(): boolean;
|
|
204
|
+
/** The selected pane fills the view; false when nothing changed. The state is
|
|
205
|
+
* closed at once; with `wt-animate` on the shell root the grid columns slide
|
|
206
|
+
* over `--dur-standard` before the other pane leaves the grid. */
|
|
207
|
+
close(): boolean;
|
|
208
|
+
/** Close one side; the other pane fills the view. */
|
|
209
|
+
closeSide(side: PaneSide): boolean;
|
|
210
|
+
/** Set the left share; `commit` writes the record. False when refused. */
|
|
211
|
+
setRatio(ratio: number, commit: boolean): boolean;
|
|
212
|
+
onChange(cb: (state: SplitState) => void): Unsubscribe;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** How the page's attention surfaces are bound. */
|
|
216
|
+
export interface AttentionOptions {
|
|
217
|
+
/** Swap every `link[rel~="icon"]` href to the reported variant: the `favicon`
|
|
218
|
+
* token of the filename gains `-<icon>` (`/favicon.svg` becomes
|
|
219
|
+
* `/favicon-input.svg`, `/favicon-32x32.png` becomes `/favicon-input-32x32.png`),
|
|
220
|
+
* so the consumer serves one file per variant it reports; a link whose filename
|
|
221
|
+
* does not start with `favicon` is left alone. Off by default because the
|
|
222
|
+
* variants are the consumer's own assets. */
|
|
223
|
+
readonly icons: boolean;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** What wants the user across the page, as one feature reports it. */
|
|
227
|
+
export interface AttentionState {
|
|
228
|
+
/** How many sessions want the user: the document title's `(N) ` prefix and the
|
|
229
|
+
* installed app's badge number. 0 clears both. */
|
|
230
|
+
readonly count: number;
|
|
231
|
+
/** The icon variant for the most pressing of them, or null for the page's own
|
|
232
|
+
* icon. Applied only under `AttentionOptions.icons`. */
|
|
233
|
+
readonly icon: string | null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Renders an attention state onto the document. */
|
|
237
|
+
export interface AttentionReporter {
|
|
238
|
+
/** Render `state`. Idempotent: a state equal to the last one reported touches
|
|
239
|
+
* nothing, so a status sweep may call it every tick. */
|
|
240
|
+
report(state: AttentionState): void;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** The notification a session status event carries (OSC 9 `<message>`). Both
|
|
244
|
+
* members are optional: an older server, or a status with no notification,
|
|
245
|
+
* omits them. */
|
|
246
|
+
export interface NotificationEvent {
|
|
247
|
+
readonly id: string;
|
|
248
|
+
readonly notification?: string | undefined;
|
|
249
|
+
readonly notificationSeq?: number | undefined;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** What the reporting feature knows about the session at delivery time. */
|
|
253
|
+
export interface NotificationView {
|
|
254
|
+
/** The session's tab is on screen. Folded with page visibility: a notification
|
|
255
|
+
* is suppressed ONLY when the user is already looking at its terminal. */
|
|
256
|
+
readonly sessionIsActive: boolean;
|
|
257
|
+
/** The notification's title. */
|
|
258
|
+
readonly label: string;
|
|
259
|
+
/** Show the session in the page; run when the user clicks the notification. */
|
|
260
|
+
readonly activate: () => void;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** The page's browser notifier. One per terminal, so one permission prompt per
|
|
264
|
+
* page whichever feature reports the gesture. The notification text is UNTRUSTED
|
|
265
|
+
* program output and reaches the browser's own surface as data, never the DOM. */
|
|
266
|
+
export interface Notifier {
|
|
267
|
+
/** Deliver one status event's notification, if it carries a new one. True when
|
|
268
|
+
* a browser notification was posted; false for every degraded path (no
|
|
269
|
+
* notification in the event, a repeat, suppression, no API, no permission, a
|
|
270
|
+
* throwing constructor). */
|
|
271
|
+
deliver(ev: NotificationEvent, view: NotificationView): boolean;
|
|
272
|
+
/** Note that this page has a session capable of notifying, which is what makes
|
|
273
|
+
* a permission prompt worth raising at all. */
|
|
274
|
+
arm(): void;
|
|
275
|
+
/** Note a user gesture: the ONLY moment a permission prompt may be raised. */
|
|
276
|
+
gesture(): void;
|
|
277
|
+
/** Drop a closed session's dedupe state. */
|
|
278
|
+
forget(id: string): void;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** The shell a feature reaches through `ctx.shell`: the pane kernels and the
|
|
282
|
+
* facts shared between them. */
|
|
283
|
+
export interface ShellContext {
|
|
284
|
+
/** The shell root; the pane root in the direct-root topology. */
|
|
285
|
+
readonly root: HTMLElement;
|
|
286
|
+
/** Null while that side has no built kernel. */
|
|
287
|
+
pane(side: PaneSide): PaneHandle | null;
|
|
288
|
+
/** Built panes, left first. */
|
|
289
|
+
panes(): readonly PaneHandle[];
|
|
290
|
+
selected(): PaneSide;
|
|
291
|
+
/** False for an invalid value and for a failed, hidden or absent pane, and for
|
|
292
|
+
* an empty pane while another pane shows a tab. While no pane shows one (both
|
|
293
|
+
* emptied, a replacement on its way) an empty pane may be selected, so the
|
|
294
|
+
* divider faces the pane the new tab is headed for. */
|
|
295
|
+
select(side: PaneSide): boolean;
|
|
296
|
+
onSelectionChange(cb: (side: PaneSide) => void): Unsubscribe;
|
|
297
|
+
/** A pane built, hidden, shown or torn down. */
|
|
298
|
+
onPanesChange(cb: () => void): Unsubscribe;
|
|
299
|
+
/** Where a click on `sessionId`'s tab would show it: the first empty pane, left
|
|
300
|
+
* then right, else the selected pane. */
|
|
301
|
+
targetFor(sessionId: string): PaneSide;
|
|
302
|
+
/** The second half of closing a shown tab: `forgetSession(sessionId)` on every
|
|
303
|
+
* built pane's connection other than `side` (whose own `clearActiveSession()`
|
|
304
|
+
* already did it), and the scrollback keeper's forget. */
|
|
305
|
+
dropSessionExcept(sessionId: string, side: PaneSide): void;
|
|
306
|
+
/** Bind the page's attention surfaces (the document-title prefix, the app
|
|
307
|
+
* badge, the icon links) and get the reporter that drives them. The terminal
|
|
308
|
+
* is the only writer of `document.title`: a program's OSC 0/2 title replaces
|
|
309
|
+
* the base and keeps the prefix. The surfaces are restored when the page hides
|
|
310
|
+
* and on destroy, and repainted on a back-forward cache restore. Keep the
|
|
311
|
+
* report an AGGREGATE, since a page has one title over many sessions. */
|
|
312
|
+
attention(opts: AttentionOptions): AttentionReporter;
|
|
313
|
+
/** Subscribe to the server's session status stream at `path`. The terminal
|
|
314
|
+
* holds ONE stream per path for the page and fans it out, because the server
|
|
315
|
+
* caps its stream subscribers; the first subscriber opens it and the last
|
|
316
|
+
* one's unsubscribe closes it, as does destroy. A subscriber joining a stream
|
|
317
|
+
* that has already opened has its `onOpen` called at once, since the stream
|
|
318
|
+
* carries only future changes and the open is what a consumer resyncs on. */
|
|
319
|
+
subscribeStatus(path: string, callbacks: StatusStreamCallbacks): Unsubscribe;
|
|
320
|
+
/** The page's one browser notifier. A feature reports the events it sees
|
|
321
|
+
* through `deliver`, a notification-capable session through `arm` and a user
|
|
322
|
+
* gesture through `gesture`; the terminal decides whether to post and when to
|
|
323
|
+
* ask for permission. */
|
|
324
|
+
readonly notifications: Notifier;
|
|
325
|
+
readonly split: SplitController;
|
|
326
|
+
/** The layout owner's boot path: open the split at the share and on the side a
|
|
327
|
+
* restored record remembers, where `split.open()` resets them to 0.5 and left.
|
|
328
|
+
* The selection is part of the open and is not announced as a change; it may
|
|
329
|
+
* rest on `selected` while that pane is still empty, as it may whenever no pane
|
|
330
|
+
* shows a tab. False when the split is open already, `committedRatio` is not a
|
|
331
|
+
* share of 0 to 1, or `selected` is not a side. */
|
|
332
|
+
restoreSplit(committedRatio: number, selected: PaneSide): boolean;
|
|
333
|
+
}
|
|
146
334
|
|
|
147
335
|
/** The inbound-wire + lifecycle events features can subscribe to via ctx.on.
|
|
148
336
|
* No `unknown`: each event carries a typed payload. */
|
|
@@ -169,13 +357,11 @@ export interface TerminalEvents {
|
|
|
169
357
|
"render:cursor": undefined;
|
|
170
358
|
}
|
|
171
359
|
|
|
172
|
-
// --- Accessibility primitives (kernel-owned, single source) ---
|
|
173
|
-
|
|
174
360
|
/** Controls the ARIA tablist/tabpanel seam on the kernel's output surface. The
|
|
175
361
|
* kernel owns the tabpanel (its output surface); `tabs` registers each tab
|
|
176
362
|
* button through this so it never crosses into kernel-owned DOM. */
|
|
177
363
|
export interface TablistController {
|
|
178
|
-
/** The
|
|
364
|
+
/** The id of the tabpanel a click on an unshown tab would fill. */
|
|
179
365
|
panelId(): string;
|
|
180
366
|
/** Register a tab button: sets role=tab + aria-controls on it and returns a
|
|
181
367
|
* handle to update its selected state and label, or remove it. */
|
|
@@ -184,39 +370,35 @@ export interface TablistController {
|
|
|
184
370
|
|
|
185
371
|
/** Handle to one registered tab's ARIA state. */
|
|
186
372
|
export interface TabHandle {
|
|
187
|
-
/** Mark this tab selected; the kernel points
|
|
373
|
+
/** Mark this tab selected; the kernel points its pane's aria-labelledby at it. */
|
|
188
374
|
setSelected(selected: boolean): void;
|
|
375
|
+
/** Point aria-controls at the tabpanel of the pane showing this tab, or (null)
|
|
376
|
+
* at the one a click would fill. */
|
|
377
|
+
setPanel(side: PaneSide | null): void;
|
|
378
|
+
/** aria-expanded: whether this tab is shown while the split is open; null
|
|
379
|
+
* removes the attribute, for a closed split. */
|
|
380
|
+
setExpanded(state: boolean | null): void;
|
|
189
381
|
/** Set the accessible label (used for aria-labelledby on the panel). */
|
|
190
382
|
setLabel(text: string): void;
|
|
191
|
-
/** Enter or leave inline-edit mode.
|
|
192
|
-
* `role="tab"`
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
* One atomic transition each way. On: `role` and `aria-selected` are removed
|
|
197
|
-
* and the chip leaves the roving sequence while the field owns focus. Off: the
|
|
198
|
-
* role is restored along with `aria-selected` and the roving tabindex derived
|
|
199
|
-
* from `selected` AS OF NOW — the active tab can change while an edit is open
|
|
200
|
-
* on a different chip, so the caller passes the current state rather than the
|
|
201
|
-
* handle replaying the state it saw at entry. */
|
|
383
|
+
/** Enter or leave inline-edit mode. ARIA marks a tab's children presentational,
|
|
384
|
+
* so a textbox inside a `role="tab"` may get no accessibility-tree node; the
|
|
385
|
+
* chip drops the tab semantics for the duration and takes `selected` AS OF
|
|
386
|
+
* NOW on the way back, since the active tab can change while an edit is open
|
|
387
|
+
* on another chip. */
|
|
202
388
|
setEditing(editing: boolean, selected: boolean): void;
|
|
203
389
|
/** Deregister this tab. */
|
|
204
390
|
remove(): void;
|
|
205
391
|
}
|
|
206
392
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
/** Everything a feature is given at setup. The only surface features use to
|
|
210
|
-
* affect the terminal; it closes the round-3 holes (typed APIs by reference,
|
|
211
|
-
* ctx.use tokens, a typed bus, a defined lifecycle, kernel-owned a11y). */
|
|
393
|
+
/** Everything a feature is given at setup: the only surface a feature uses to
|
|
394
|
+
* affect the terminal. */
|
|
212
395
|
export interface TerminalContext {
|
|
213
396
|
/** Mount chrome into a named kernel region; DOM order in a region equals
|
|
214
397
|
* visual order. Returns a live element the feature appends into. */
|
|
215
398
|
region(name: RegionName, slot?: RegionSlot): HTMLElement;
|
|
216
|
-
/** The terminal scroll surface, for
|
|
217
|
-
* gestures (contextMenu right-click / long-press, the tabs swipe) or scope a
|
|
399
|
+
/** The terminal scroll surface, for surface-level gestures and for scoping a
|
|
218
400
|
* selection to the output. Read-only use; features own only their region
|
|
219
|
-
* chrome
|
|
401
|
+
* chrome. */
|
|
220
402
|
surface(): HTMLElement;
|
|
221
403
|
|
|
222
404
|
/** The single sanitizing, session-routed input path: bracket, strip control
|
|
@@ -243,11 +425,26 @@ export interface TerminalContext {
|
|
|
243
425
|
readonly render: RenderHandle;
|
|
244
426
|
/** Drive the engine scroll controller. */
|
|
245
427
|
readonly scroll: ScrollHandle;
|
|
428
|
+
/** Read the engine's mode state (bracketed paste, mouse tracking, DECCKM). */
|
|
429
|
+
readonly modes: ModeReaders;
|
|
246
430
|
/** Read-only active-session view. */
|
|
247
431
|
readonly session: SessionView;
|
|
248
|
-
|
|
249
|
-
|
|
432
|
+
/** The shell this feature's pane belongs to. A feature with `scope: "shell"`
|
|
433
|
+
* receives a context whose `surface`, `render`, `scroll`, `modes`, `session`,
|
|
434
|
+
* `send`, `paste`, `toast` and `announce` resolve to the SELECTED pane at call
|
|
435
|
+
* time, whose `region` is the shell's own, and whose `on` is the union of every
|
|
436
|
+
* pane's bus; `ctx.shell.pane(side)` reaches one pane explicitly. */
|
|
437
|
+
readonly shell: ShellContext;
|
|
438
|
+
|
|
439
|
+
/** Subscribe to a typed terminal event. Released with the feature. */
|
|
250
440
|
on<K extends keyof TerminalEvents>(e: K, fn: (p: TerminalEvents[K]) => void): Unsubscribe;
|
|
441
|
+
/** Hand the terminal a release for something this feature acquired OUTSIDE
|
|
442
|
+
* `ctx` (a timer, an observer, a `document` or `window` listener, an outside
|
|
443
|
+
* subscription), on the line that acquires it. The terminal runs every
|
|
444
|
+
* release, last registered first, when this feature's `setup` throws and after
|
|
445
|
+
* its `teardown()` returns, so a half-built feature leaks nothing and a
|
|
446
|
+
* `teardown` never re-lists what `defer` already holds. */
|
|
447
|
+
defer(release: () => void): void;
|
|
251
448
|
|
|
252
449
|
/** Look up a peer feature's typed API by its factory value, or undefined if
|
|
253
450
|
* that feature is absent or not yet set up. Read lazily (at interaction
|
|
@@ -255,142 +452,119 @@ export interface TerminalContext {
|
|
|
255
452
|
use<A>(feature: TerminalFeature<A>): A | undefined;
|
|
256
453
|
|
|
257
454
|
/** Show a transient toast on the kernel-owned toast surface (a shared
|
|
258
|
-
* primitive
|
|
455
|
+
* primitive), so a feature signals "Copied" etc. without
|
|
259
456
|
* owning its own surface and without needing connectionBanner present. */
|
|
260
457
|
toast(message: string, ms?: number): void;
|
|
261
458
|
/** Announce a message on the single kernel-owned polite (or assertive) live
|
|
262
459
|
* region, so features do not spawn competing aria-live regions. */
|
|
263
460
|
announce(message: string, politeness?: "polite" | "assertive"): void;
|
|
264
|
-
/** Report WHY startup is still waiting, onto the consumer's loading overlay
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
* Supersedes the kernel's own scripted wording (and stops its rotation),
|
|
271
|
-
* because a reason the SERVER gave is always better than a guess -- pass the
|
|
272
|
-
* server's own message where there is one. Safe to call on every tick of a
|
|
273
|
-
* retry loop: an unchanged message is a no-op, so a screen reader is not
|
|
274
|
-
* re-read the same sentence every few seconds. Also a no-op once the overlay
|
|
275
|
-
* has been dismissed, so a late retry cannot resurrect it. */
|
|
461
|
+
/** Report WHY startup is still waiting, onto the consumer's loading overlay,
|
|
462
|
+
* which covers every other surface a feature could speak through before the
|
|
463
|
+
* first frame. Supersedes the kernel's scripted wording; pass the server's own
|
|
464
|
+
* message where there is one. An unchanged message is a no-op, so a screen
|
|
465
|
+
* reader is not re-read the same sentence every tick, and so is a call after
|
|
466
|
+
* the overlay has been dismissed. */
|
|
276
467
|
loadingReason(message: string): void;
|
|
277
|
-
/** Set the prefix the kernel composes onto the document title, or "" to clear
|
|
278
|
-
* it. The kernel is the only writer of `document.title`: it holds the base
|
|
279
|
-
* (the served `<title>`, replaced by a program's OSC 0/2 window title) and
|
|
280
|
-
* this prefix, and re-composes on either change. A feature that assigned
|
|
281
|
-
* `document.title` directly would be erased by the next OSC 2 from any shell
|
|
282
|
-
* or editor running in a session, with no ordering that avoids it.
|
|
283
|
-
*
|
|
284
|
-
* Idempotent and safe on every status tick: an unchanged result is not
|
|
285
|
-
* re-assigned, because the title doubles as the browser-tab label and the
|
|
286
|
-
* bookmark name. Cleared automatically when the terminal is destroyed.
|
|
287
|
-
*
|
|
288
|
-
* Keep it an AGGREGATE. A page has one title while this UI multiplexes many
|
|
289
|
-
* sessions, so any per-session value has to pick a session arbitrarily (see
|
|
290
|
-
* the standing decision against writing progress here in features/tabs). */
|
|
291
|
-
titlePrefix(text: string): void;
|
|
292
468
|
/** The kernel's tablist/tabpanel ARIA controller (used by tabs). */
|
|
293
469
|
tablist(): TablistController;
|
|
294
470
|
|
|
295
|
-
/** Construct a LineStore honoring
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
* drift.
|
|
301
|
-
*
|
|
302
|
-
* Pass the session id the store belongs to. It is optional only so an
|
|
303
|
-
* existing feature keeps compiling; a feature that owns sessions should
|
|
304
|
-
* always pass it, because it is what lets the kernel return a store HYDRATED
|
|
305
|
-
* from `CreateTerminalOptions.persistScrollback` (and register it for
|
|
306
|
-
* saving). Omitting it yields a correct but always-empty store, so that
|
|
307
|
-
* feature's sessions silently opt out of persistence — the kernel warns once
|
|
308
|
-
* when persistence is enabled and an id is withheld. */
|
|
471
|
+
/** Construct a LineStore honoring `CreateTerminalOptions.scrollbackLines`, so
|
|
472
|
+
* one option governs the implicit store and every per-tab store alike. Pass
|
|
473
|
+
* the session id: it is what lets the store be HYDRATED from
|
|
474
|
+
* `persistScrollback` and registered for saving. Omitting it yields a correct
|
|
475
|
+
* but never-persisted store, warned once when persistence is enabled. */
|
|
309
476
|
newLineStore(sessionId?: string): LineStore;
|
|
310
477
|
|
|
311
|
-
/** The
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
* class exposes to CSS; root size, not viewport size, so an embedded
|
|
316
|
-
* terminal in a narrow panel counts as narrow. `coarse` is the primary
|
|
317
|
-
* pointer's coarseness (a live media-query read). Read lazily at
|
|
318
|
-
* interaction time. */
|
|
478
|
+
/** The layout facts a feature keys touch-vs-desktop behavior on. `narrow` is a
|
|
479
|
+
* ROOT compact in EITHER dimension (the `.wt-narrow` class), root size rather
|
|
480
|
+
* than viewport size so an embedded panel counts; `coarse` is a live read of
|
|
481
|
+
* the primary pointer. */
|
|
319
482
|
layout(): { narrow: boolean; coarse: boolean };
|
|
320
483
|
|
|
321
|
-
/** Switch the live terminal to a session.
|
|
322
|
-
* session's
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
* before input resumes), and emits session:switch for pure observers
|
|
326
|
-
* (sections 5, 22.4). Updates the active session the SessionView reports.
|
|
327
|
-
* This is how tabs drives the reconnect-on-switch swap without touching the
|
|
328
|
-
* raw connection layer (section 22.9). */
|
|
484
|
+
/** Switch the live terminal to a session. The caller binds the renderer to the
|
|
485
|
+
* session's store first; the kernel re-points the connection, invokes every
|
|
486
|
+
* feature's onSwitch before input resumes, and emits session:switch for pure
|
|
487
|
+
* observers. */
|
|
329
488
|
notifySwitch(session: SessionRef): void;
|
|
330
|
-
/** Drop a session's
|
|
331
|
-
*
|
|
332
|
-
* layer; features never touch it directly. */
|
|
489
|
+
/** Drop a closed session's resume state on every pane's connection and in the
|
|
490
|
+
* scrollback keeper. */
|
|
333
491
|
dropSession(id: string): void;
|
|
334
492
|
|
|
335
493
|
/** Observe feature errors (a feature's runtime callback threw). */
|
|
336
494
|
onError(fn: (feature: string, err: unknown) => void): Unsubscribe;
|
|
337
495
|
}
|
|
338
496
|
|
|
339
|
-
// --- The feature interface ---
|
|
340
|
-
|
|
341
497
|
/** A feature is a factory value implementing this: a name and a setup the
|
|
342
498
|
* kernel runs once. `Api` is the feature's own typed public API (void when it
|
|
343
499
|
* exposes none). */
|
|
344
500
|
export interface TerminalFeature<Api = void> {
|
|
345
501
|
readonly name: string;
|
|
502
|
+
/** Build the feature. A `setup` that throws or rejects releases nothing itself:
|
|
503
|
+
* everything taken through `ctx` is released by the terminal, and everything
|
|
504
|
+
* acquired outside `ctx` must be handed to `ctx.defer` at acquisition or it
|
|
505
|
+
* leaks. */
|
|
346
506
|
setup(ctx: TerminalContext): FeatureInstance<Api> | Promise<FeatureInstance<Api>>;
|
|
347
|
-
/**
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
|
|
351
|
-
|
|
507
|
+
/** `"shell"` for a feature set up ONCE per terminal with the shell context (the
|
|
508
|
+
* tab row, the key toolbar, the status monitor, the animation class); absent for
|
|
509
|
+
* a feature set up once per pane. */
|
|
510
|
+
readonly scope?: "shell";
|
|
511
|
+
/** Populated after setup with the instance's api, so a consumer holding the
|
|
512
|
+
* feature value can read it (`tabs.api?.create()`); undefined until then.
|
|
513
|
+
* Readonly so a feature is covariant in Api. */
|
|
352
514
|
readonly api?: Api;
|
|
353
|
-
/** Present on the ONE feature that owns session selection
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
*
|
|
358
|
-
* feature setup completes. Single-terminal presets leave it unset so the
|
|
359
|
-
* kernel connects to the bare wsPath at startup. */
|
|
515
|
+
/** Present on the ONE feature that owns session selection; createTerminal
|
|
516
|
+
* throws when two features register. Its presence makes the terminal SKIP
|
|
517
|
+
* the startup connect to the bare wsPath (which a session-gated server 404s)
|
|
518
|
+
* and drive the first connect through resolveInitialSession() instead. A
|
|
519
|
+
* feature sets this OR `paneLayoutOwner`, never both. */
|
|
360
520
|
readonly sessionOwner?: SessionOwnerRegistration;
|
|
521
|
+
/** Present on the feature that owns which session each pane shows (the tabs
|
|
522
|
+
* feature). Like `sessionOwner` it makes the terminal skip the bare startup
|
|
523
|
+
* connect; unlike it, the owner shows the initial tabs itself through the pane
|
|
524
|
+
* handles. At most one owner of either kind per terminal. */
|
|
525
|
+
readonly paneLayoutOwner?: PaneLayoutOwnerRegistration;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/** The pane-layout-owner registration: the tabs feature's half of the first
|
|
529
|
+
* connect, where the server's layout record decides what each pane shows. */
|
|
530
|
+
export interface PaneLayoutOwnerRegistration {
|
|
531
|
+
/** Show the initial tabs through the pane handles from the server's layout
|
|
532
|
+
* record and the session list; resolve true when any pane shows a tab. Called
|
|
533
|
+
* once, after every shell-scoped setup has resolved. A false or a throw means
|
|
534
|
+
* nothing could be shown; the terminal dismisses the loading overlay so the
|
|
535
|
+
* owner's retry chrome is visible. */
|
|
536
|
+
resolveInitialLayout(): Promise<boolean>;
|
|
537
|
+
/** The session shown on `side`, or null for an empty, hidden or failed pane. */
|
|
538
|
+
shownIn(side: PaneSide): string | null;
|
|
539
|
+
/** Show a tab in the pane on `side`, emptying the pane that showed it before:
|
|
540
|
+
* how the terminal hands a tab to a survivor when the pane showing it leaves.
|
|
541
|
+
* The split's own state is the terminal's and is not touched. False when
|
|
542
|
+
* refused: an unknown id, or a side whose pane is absent, failed or hidden. */
|
|
543
|
+
showIn(side: PaneSide, id: string): boolean;
|
|
361
544
|
}
|
|
362
545
|
|
|
363
|
-
/**
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
* itself, so a failed bootstrap is SEEN by the kernel (which dismisses the
|
|
367
|
-
* loading overlay) instead of inferred from a missing side effect. */
|
|
546
|
+
/** How the one session-owning feature and the terminal split the first connect:
|
|
547
|
+
* the feature owns session selection and its bootstrap state, the terminal owns
|
|
548
|
+
* the connect, so a failed bootstrap is SEEN rather than inferred. */
|
|
368
549
|
export interface SessionOwnerRegistration {
|
|
369
|
-
/** Resolve the initial session
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
* feature
|
|
375
|
-
* loading overlay so that chrome is visible. A throw is treated as null
|
|
376
|
-
* (and reported through the feature-error channel). Called exactly once,
|
|
377
|
-
* after every feature's setup has resolved. */
|
|
550
|
+
/** Resolve the initial session and bind the renderer to its store, but do NOT
|
|
551
|
+
* call ctx.notifySwitch for it: the terminal performs the switch with the
|
|
552
|
+
* returned ref. Null (or a throw, reported through the error channel) means
|
|
553
|
+
* the bootstrap failed; the terminal dismisses the loading overlay so the
|
|
554
|
+
* feature's retry chrome is visible. Called exactly once, after every
|
|
555
|
+
* feature's setup has resolved. */
|
|
378
556
|
resolveInitialSession(): Promise<SessionRef | null>;
|
|
379
557
|
}
|
|
380
558
|
|
|
381
|
-
/** What a feature's setup returns
|
|
382
|
-
* optional onSwitch the kernel invokes on a tab switch. */
|
|
559
|
+
/** What a feature's setup returns. */
|
|
383
560
|
export interface FeatureInstance<Api = void> {
|
|
384
561
|
/** This feature's public API, surfaced on the feature value and via ctx.use. */
|
|
385
562
|
readonly api?: Api;
|
|
386
563
|
/** Remove this feature's DOM and listeners. Run in reverse order on destroy. */
|
|
387
564
|
teardown(): void;
|
|
388
|
-
/** Called
|
|
389
|
-
*
|
|
390
|
-
*
|
|
391
|
-
* toolbar disarms its one-shot sticky-Ctrl here so a pending Ctrl cannot
|
|
392
|
-
* become an accidental Ctrl+C to the wrong agent (design 5.1). Runs for every
|
|
393
|
-
* feature before any onSwitch. */
|
|
565
|
+
/** Called at the START of a tab switch, before the connection is re-pointed,
|
|
566
|
+
* so latched input state (a pending sticky-Ctrl) cannot fire against the
|
|
567
|
+
* incoming session. Runs for every feature before any onSwitch. */
|
|
394
568
|
onDetach?(): void;
|
|
395
569
|
/** Called by the kernel on a tab switch (attach), after the connection is
|
|
396
570
|
* re-pointed, for features that must re-point before input resumes
|
|
@@ -398,31 +572,13 @@ export interface FeatureInstance<Api = void> {
|
|
|
398
572
|
onSwitch?(session: SessionRef): void;
|
|
399
573
|
}
|
|
400
574
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
* both happened at the CALL SITE, outside this boundary, so a consumer really
|
|
409
|
-
* did need its own surface for those two — and every consumer that built one
|
|
410
|
-
* restated this library's copy and re-decided its focus and ARIA behavior.
|
|
411
|
-
* createTerminal now takes a selector and a feature THUNK, which pulls both
|
|
412
|
-
* failures inside, and the promise holds literally.
|
|
413
|
-
*
|
|
414
|
-
* - `kernel-init`: a SYNCHRONOUS throw out of createTerminal (an unresolvable
|
|
415
|
-
* mount selector, a throwing preset or feature constructor, an invalid
|
|
416
|
-
* feature list, a DOM invariant). The kernel renders the recovery surface and
|
|
417
|
-
* then RETHROWS, so a caller with its own handling still sees the error.
|
|
418
|
-
* - `feature-setup`: an async feature-composition failure. The kernel has
|
|
419
|
-
* already stopped the connection, released every listener and singleton,
|
|
420
|
-
* torn down completed features, and cleared the terminal root when it
|
|
421
|
-
* delivers this value. Nothing is rethrown; the failure is asynchronous.
|
|
422
|
-
*
|
|
423
|
-
* Discriminate on `phase`: `feature-setup` names the offending feature,
|
|
424
|
-
* `kernel-init` has no feature to name because composition never began, and
|
|
425
|
-
* only `kernel-init` can carry an undefined `surface`. */
|
|
575
|
+
/** A fatal failure while createTerminal is starting up, delivered so a consumer
|
|
576
|
+
* never hand-builds its own startup-failure surface. `kernel-init` is a
|
|
577
|
+
* SYNCHRONOUS throw out of createTerminal (the recovery surface is rendered,
|
|
578
|
+
* then the error is RETHROWN); `feature-setup` is an async composition failure,
|
|
579
|
+
* delivered after the failed pane has released everything and cleared its root,
|
|
580
|
+
* with nothing rethrown. An undefined `surface` says there is nowhere to
|
|
581
|
+
* render, in either phase. */
|
|
426
582
|
export type TerminalStartupFailure =
|
|
427
583
|
| {
|
|
428
584
|
readonly phase: "feature-setup";
|
|
@@ -430,37 +586,31 @@ export type TerminalStartupFailure =
|
|
|
430
586
|
readonly feature: string;
|
|
431
587
|
/** The original thrown or rejected value. */
|
|
432
588
|
readonly cause: unknown;
|
|
433
|
-
/** The element the built-in surface would fill
|
|
434
|
-
*
|
|
435
|
-
|
|
589
|
+
/** The element the built-in surface would fill, and the element a handler
|
|
590
|
+
* claiming the surface (returning true) must render into: the terminal
|
|
591
|
+
* root, or under `split` the failed second pane's root while it stays in
|
|
592
|
+
* the grid. `undefined` when a second pane fails after the split closed:
|
|
593
|
+
* its tab has moved to the healthy pane, which fills the view, and the
|
|
594
|
+
* failed pane is discarded with no panel. */
|
|
595
|
+
readonly surface: HTMLElement | undefined;
|
|
436
596
|
}
|
|
437
597
|
| {
|
|
438
598
|
readonly phase: "kernel-init";
|
|
439
599
|
/** The original thrown value, rethrown to the caller after this returns. */
|
|
440
600
|
readonly cause: unknown;
|
|
441
601
|
/** The element the built-in surface would fill, and the element a handler
|
|
442
|
-
* claiming the surface (returning true) must render into
|
|
443
|
-
* resolved root
|
|
444
|
-
*
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
* a missing mount target is reported and rethrown but never answered by
|
|
448
|
-
* seizing the host's page. A handler that renders its own UI must
|
|
449
|
-
* therefore check for undefined. */
|
|
602
|
+
* claiming the surface (returning true) must render into: usually the
|
|
603
|
+
* resolved root. `undefined` in two cases, so a handler must check: an
|
|
604
|
+
* unresolvable mount target in `container` layout (an embedded terminal
|
|
605
|
+
* never seizes its host page), and a document that already holds a live
|
|
606
|
+
* terminal, in either layout (the root may be that terminal's). */
|
|
450
607
|
readonly surface: HTMLElement | undefined;
|
|
451
608
|
};
|
|
452
609
|
|
|
453
610
|
/** One stored scrollback entry: the engine's store snapshot plus the timestamp
|
|
454
|
-
* the library stamps on it.
|
|
455
|
-
*
|
|
456
|
-
*
|
|
457
|
-
* is ENFORCED here rather than documented as someone else's job. The case that
|
|
458
|
-
* motivates persisting at all — iOS discarding a backgrounded tab — is exactly
|
|
459
|
-
* the case where no "closed cleanly" path ever runs, so without an age bound
|
|
460
|
-
* the consumer's storage accumulates snapshots for sessions that stopped
|
|
461
|
-
* existing weeks ago. A rejected entry is also dropped through
|
|
462
|
-
* `ScrollbackPersistence.drop`, which makes collection-on-access automatic; a
|
|
463
|
-
* consumer that wants to sweep proactively has `savedAt` to sweep by. */
|
|
611
|
+
* the library stamps on it. The timestamp is the library's because the maximum
|
|
612
|
+
* age is ENFORCED here: the case that motivates persisting (iOS discarding a
|
|
613
|
+
* backgrounded tab) is the case where no "closed cleanly" path ever runs. */
|
|
464
614
|
export interface PersistedScrollback {
|
|
465
615
|
/** `Date.now()` when the library wrote this entry. */
|
|
466
616
|
readonly savedAt: number;
|
|
@@ -468,233 +618,113 @@ export interface PersistedScrollback {
|
|
|
468
618
|
readonly snapshot: StoreSnapshot;
|
|
469
619
|
}
|
|
470
620
|
|
|
471
|
-
/** The consumer-supplied storage seam for scrollback persistence
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
*
|
|
479
|
-
* storage has no meaningful at-rest protection, which makes "where does this
|
|
480
|
-
* live, and for how long" a decision for the application, not its terminal
|
|
481
|
-
* widget.
|
|
482
|
-
*
|
|
483
|
-
* `sessionId` is a real session id, never a key of the consumer's invention:
|
|
484
|
-
* the same id the tabs feature uses, or — for a single unmanaged terminal — the
|
|
485
|
-
* engine's per-tab id from `connection.currentSessionId()`. That is load-bearing
|
|
486
|
-
* rather than tidy, because the id is also what the persisted server epoch is
|
|
487
|
-
* adopted against. A consumer may of course prefix it inside its own storage.
|
|
488
|
-
*
|
|
489
|
-
* `load` is SYNCHRONOUS, which is a real constraint and worth understanding
|
|
490
|
-
* before fighting it: hydration has to complete before the resume goes out, and
|
|
491
|
-
* a resume is not restartable — a store hydrated after `haveThrough` was sent
|
|
492
|
-
* has already lost the argument. A consumer whose storage is asynchronous
|
|
493
|
-
* (IndexedDB) reads its snapshots into memory before calling `createTerminal`
|
|
494
|
-
* and answers from there, which is also the shape that holds no database
|
|
495
|
-
* connection open.
|
|
496
|
-
*
|
|
497
|
-
* Every callback may throw or return nonsense without consequence: the library
|
|
498
|
-
* treats any failure as "nothing was restored" and takes a full resume, exactly
|
|
499
|
-
* as if persistence were switched off. */
|
|
621
|
+
/** The consumer-supplied storage seam for scrollback persistence. Storage is the
|
|
622
|
+
* CONSUMER's because a held IndexedDB connection costs bfcache eligibility,
|
|
623
|
+
* which this library depends on, and because scrollback holds secrets while
|
|
624
|
+
* browser storage has no at-rest protection. `sessionId` is a real session id,
|
|
625
|
+
* the one the persisted server epoch is adopted against. `load` is SYNCHRONOUS:
|
|
626
|
+
* a resume is not restartable, so hydration must complete before it goes out;
|
|
627
|
+
* asynchronous storage is read into memory before `createTerminal`. Every
|
|
628
|
+
* callback may throw: any failure means "nothing restored" and a full resume. */
|
|
500
629
|
export interface ScrollbackPersistence {
|
|
501
|
-
/**
|
|
502
|
-
*
|
|
503
|
-
*
|
|
504
|
-
*
|
|
505
|
-
*
|
|
506
|
-
* an entry coming BACK has been outside this program's memory and is a claim.
|
|
507
|
-
* Declaring the proven type on both sides told the compiler the returned value
|
|
508
|
-
* was verified — which no implementation of this seam can promise, this
|
|
509
|
-
* library's own `localScrollbackStorage` least of all, since it hands back a
|
|
510
|
-
* `JSON.parse` result. The kernel parsed it anyway, and because the type said
|
|
511
|
-
* otherwise it did so in helpers that each re-established the shape from
|
|
512
|
-
* scratch: measured 2026-08, 15 of `scrollback.ts`'s 19 permanently unkillable
|
|
513
|
-
* mutants were those duplicate checks.
|
|
514
|
-
*
|
|
515
|
-
* Nothing is lost by an implementation: a `load` annotated
|
|
516
|
-
* `PersistedScrollback | null` still satisfies this, so a consumer that holds
|
|
517
|
-
* real entries in memory keeps its own type checking. What goes is only the
|
|
518
|
-
* false guarantee at the boundary that cannot honour it.
|
|
519
|
-
*
|
|
520
|
-
* `PersistedScrollback` remains the shape to store and the shape you get back
|
|
521
|
-
* when storage is intact; see `save`. */
|
|
630
|
+
/** Whatever was stored for a session, or null/undefined. Called once per
|
|
631
|
+
* session, before that session connects. `unknown`, not `PersistedScrollback`:
|
|
632
|
+
* an entry coming BACK has been outside this program's memory and is a claim,
|
|
633
|
+
* which the library's own `JSON.parse`-backed storage cannot promise more of.
|
|
634
|
+
* A `load` annotated `PersistedScrollback | null` still satisfies this. */
|
|
522
635
|
load(sessionId: string): unknown;
|
|
523
|
-
/** Store an entry, replacing any previous one
|
|
524
|
-
*
|
|
525
|
-
*
|
|
526
|
-
*
|
|
527
|
-
* a watermark against it; a failure reported that way made the library skip the
|
|
528
|
-
* session until its output advanced again, which is how persistence stopped
|
|
529
|
-
* silently. A throw is caught, warned once, and retried on the next pass. */
|
|
636
|
+
/** Store an entry, replacing any previous one. THROW when nothing was
|
|
637
|
+
* persisted: returning normally records "this is on disk", and a failure
|
|
638
|
+
* reported that way made the library skip the session until its output
|
|
639
|
+
* advanced again. A throw is warned once and retried on the next pass. */
|
|
530
640
|
save(sessionId: string, entry: PersistedScrollback): void;
|
|
531
|
-
/** Delete a session's entry
|
|
532
|
-
*
|
|
533
|
-
* bad or expired entry is not re-read on every load. */
|
|
641
|
+
/** Delete a session's entry: on tab close, and when the library rejects a
|
|
642
|
+
* stored entry, so a bad one is not re-read on every load. */
|
|
534
643
|
drop(sessionId: string): void;
|
|
535
|
-
/** Newest lines to persist per session (default 200).
|
|
536
|
-
*
|
|
537
|
-
*
|
|
538
|
-
*
|
|
539
|
-
*
|
|
540
|
-
* The default follows the nearest precedent — VS Code's
|
|
541
|
-
* `terminal.integrated.persistentSessionScrollback` restores 100 lines — with
|
|
542
|
-
* headroom, and against measurement: 200 lines is ~60 K characters of a
|
|
543
|
-
* coloured session and serialises in under a millisecond, where 1000 is ~300 K
|
|
544
|
-
* and ~4 ms, paid on every backgrounding and every timer tick while output
|
|
545
|
-
* advances. The depth NOT kept is reported honestly after hydration: the store
|
|
546
|
-
* shows its "earlier output trimmed" marker rather than implying the buffer is
|
|
547
|
-
* complete. Non-integer or non-positive values are ignored. */
|
|
644
|
+
/** Newest lines to persist per session (default 200). A bound rather than the
|
|
645
|
+
* whole store because the cost is a repeated serialize on every backgrounding
|
|
646
|
+
* and timer tick, and the screen plus recent history is what a returning user
|
|
647
|
+
* needs; the store shows its "earlier output trimmed" marker for the rest.
|
|
648
|
+
* Non-integer or non-positive values are ignored. */
|
|
548
649
|
lines?: number;
|
|
549
|
-
/** Maximum age of a stored entry (default 7 days)
|
|
550
|
-
*
|
|
551
|
-
* whose clock moved cannot leave an entry that never expires. */
|
|
650
|
+
/** Maximum age of a stored entry (default 7 days), in EITHER direction, so a
|
|
651
|
+
* clock that moved cannot leave an entry that never expires. */
|
|
552
652
|
maxAgeMs?: number;
|
|
553
653
|
/** Interval for the background save while content is changing (default 10s).
|
|
554
|
-
*
|
|
555
|
-
*
|
|
556
|
-
* before a discard and the library writes on both, but Chrome's page-lifecycle
|
|
557
|
-
* documentation is explicit that `pagehide` is not guaranteed. The timer is
|
|
558
|
-
* what makes a killed tab have a recent-enough snapshot rather than none. It
|
|
559
|
-
* skips sessions whose content has not advanced since their last save. */
|
|
654
|
+
* `pagehide` is not guaranteed before a discard, so the timer is what makes a
|
|
655
|
+
* killed tab have a recent snapshot rather than none. */
|
|
560
656
|
saveIntervalMs?: number;
|
|
561
657
|
}
|
|
562
658
|
|
|
563
659
|
/** Options for createTerminal. */
|
|
564
660
|
export interface CreateTerminalOptions {
|
|
565
|
-
/** The feature list, as a FUNCTION
|
|
566
|
-
*
|
|
567
|
-
*
|
|
568
|
-
*
|
|
569
|
-
*
|
|
570
|
-
*
|
|
571
|
-
*
|
|
572
|
-
* before createTerminal was entered — so the throw escaped the library
|
|
573
|
-
* entirely and every consumer had to wrap the preset call in its own
|
|
574
|
-
* try/catch and hand-build a recovery surface. Write `features:
|
|
575
|
-
* presetTabbed` when the preset takes no arguments, or `features: () =>
|
|
576
|
-
* presetTabbed({...})` when it does. */
|
|
661
|
+
/** The feature list, as a FUNCTION so a preset that throws does so INSIDE
|
|
662
|
+
* createTerminal's failure boundary; omitted means the bare kernel. Invoked
|
|
663
|
+
* once per pane and must return FRESH feature objects on every call: the
|
|
664
|
+
* terminal writes each object's `api` after setup and resolves `ctx.use` on
|
|
665
|
+
* its identity, so a saved array (`const f = presetTabbed(); features: () =>
|
|
666
|
+
* f`) is refused at `kernel-init`. Features marked `scope: "shell"` are taken
|
|
667
|
+
* from the first invocation only. */
|
|
577
668
|
features?: () => readonly TerminalFeature<unknown>[];
|
|
578
|
-
/** How the terminal claims space (default "viewport")
|
|
579
|
-
*
|
|
580
|
-
*
|
|
581
|
-
* "container": the root fills its parent element, which becomes the
|
|
582
|
-
* boundary — the embedded case (vibekit's panel). Either way the kernel
|
|
583
|
-
* stamps the matching class (wt-viewport / wt-container) on the root and
|
|
669
|
+
/** How the terminal claims space (default "viewport"): a fixed full-viewport
|
|
670
|
+
* box, or a root filling its parent element (the embedded case). The
|
|
671
|
+
* matching class (wt-viewport / wt-container) is stamped on the root, and
|
|
584
672
|
* every piece of chrome positions against the root, never the page. */
|
|
585
673
|
layout?: "viewport" | "container";
|
|
586
674
|
/** WebSocket endpoint path (default "/ws"). */
|
|
587
675
|
wsPath?: string;
|
|
588
676
|
/** CSS font shorthand awaited before the first resize. */
|
|
589
677
|
fontReady?: string;
|
|
590
|
-
/** Retained scrollback lines per terminal
|
|
591
|
-
*
|
|
592
|
-
*
|
|
593
|
-
*
|
|
594
|
-
*
|
|
595
|
-
*
|
|
596
|
-
* turn bounds the height of the scrolled-contents layer the browser
|
|
597
|
-
* composites. It is a HISTORY budget floored at the live screen: the
|
|
598
|
-
* engine never evicts the current window's rows, so a value at or below
|
|
599
|
-
* the terminal height keeps the full screen and simply retains no
|
|
600
|
-
* scrollback. Choose a value comfortably above the largest expected
|
|
601
|
-
* terminal height (a few multiples of it): near or below the height,
|
|
602
|
-
* batched eviction has no history headroom to work with and degrades
|
|
603
|
-
* back to per-line eviction — the screen stays correct, but the churn
|
|
604
|
-
* the batching exists to remove returns. A memory-constrained consumer (iOS Safari, where the content
|
|
605
|
-
* process is a jetsam victim under system pressure) passes a smaller
|
|
606
|
-
* budget; scrolling back then reaches fewer lines, so this is a consumer
|
|
607
|
-
* policy choice, not a tuning knob the library second-guesses. Applies to
|
|
608
|
-
* the kernel's implicit store (via the engine renderer) and to every store
|
|
609
|
-
* created through `ctx.newLineStore()` (the tabs switching cache).
|
|
678
|
+
/** Retained scrollback lines per terminal and per tab; the engine's default is
|
|
679
|
+
* 5000. The page's dominant memory dial: it bounds the styled runs each store
|
|
680
|
+
* retains AND the DOM rows the renderer keeps. A HISTORY budget floored at the
|
|
681
|
+
* live screen, so choose a few multiples of the largest expected height: near
|
|
682
|
+
* or below it, batched eviction has no headroom and degrades to per-line
|
|
683
|
+
* churn. A memory-constrained consumer (iOS Safari) passes a smaller budget.
|
|
610
684
|
* Non-integer or non-positive values are ignored. */
|
|
611
685
|
scrollbackLines?: number;
|
|
612
686
|
/** Persist each session's scrollback across a page discard, through storage
|
|
613
|
-
* the CONSUMER supplies
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
618
|
-
*
|
|
619
|
-
*
|
|
620
|
-
* snapshot restored, the resume asks only for what was printed while the tab
|
|
621
|
-
* was gone.
|
|
622
|
-
*
|
|
623
|
-
* Scope it honestly: this helps the FRESH-LOAD case only. A warm reconnect and
|
|
624
|
-
* an in-page tab switch already replay nothing, because their stores never
|
|
625
|
-
* went away.
|
|
626
|
-
*
|
|
627
|
-
* Off by default, and the default is the LIBRARY's rather than a
|
|
628
|
-
* recommendation. `localStorage` is a shared, origin-wide, quota-limited
|
|
629
|
-
* resource, and this package is embedded in host applications that keep their
|
|
630
|
-
* own state there; consuming it uninvited would risk the embedder's writes
|
|
631
|
-
* failing, whose degradation is theirs to design and not ours to assume. Our
|
|
632
|
-
* own failure under quota pressure is graceful (nothing restored, terminal
|
|
633
|
-
* unaffected). An application decides durability for its own users; a library
|
|
634
|
-
* does not decide it for an embedder who never asked. All three reference apps
|
|
635
|
-
* DO enable it — see `localScrollbackStorage`, which is the ready-made answer.
|
|
636
|
-
*
|
|
637
|
-
* Note for a consumer weighing it: enabling this writes terminal output where
|
|
638
|
-
* it is readable from that browser without reaching the server and outlives the
|
|
639
|
-
* tab (bounded by a per-session delete on close and a seven-day expiry). No
|
|
640
|
-
* permission prompt is involved — `localStorage` needs none — and a browser
|
|
641
|
-
* that blocks site data simply restores nothing.
|
|
642
|
-
*
|
|
643
|
-
* It applies to the whole terminal, not one composition: the kernel's implicit
|
|
644
|
-
* store (a single unmanaged terminal) and every store a session-owning feature
|
|
645
|
-
* creates through `ctx.newLineStore(sessionId)` (the tabs cache) are both
|
|
646
|
-
* hydrated and both saved. */
|
|
687
|
+
* the CONSUMER supplies; see `localScrollbackStorage`. Helps the FRESH-LOAD
|
|
688
|
+
* case only (iOS evicts backgrounded tabs, and returning re-runs the page):
|
|
689
|
+
* the resume asks only for what was printed while the tab was gone. Off by
|
|
690
|
+
* default because `localStorage` is a shared, quota-limited resource an
|
|
691
|
+
* embedder may depend on, and because enabling it writes terminal output
|
|
692
|
+
* where that browser can read it without the server. Applies to the implicit
|
|
693
|
+
* store and to every `ctx.newLineStore(sessionId)` store alike. */
|
|
647
694
|
persistScrollback?: ScrollbackPersistence;
|
|
648
|
-
/** Optional pre-JS loading overlay the
|
|
649
|
-
* it the `wt-loading` class and a `wt-loading-bar` child
|
|
650
|
-
*
|
|
651
|
-
* startup drags on (see kernel/loading-status.ts). */
|
|
695
|
+
/** Optional pre-JS loading overlay the terminal fades out on first paint. Give
|
|
696
|
+
* it the `wt-loading` class and a `wt-loading-bar` child; a progressive status
|
|
697
|
+
* line is written into it while startup drags on. */
|
|
652
698
|
loading?: HTMLElement;
|
|
653
699
|
/** Reword the loading overlay's progressive status text. Partial: anything
|
|
654
|
-
* omitted keeps the library default.
|
|
655
|
-
*
|
|
656
|
-
* a waiting user reads, not about branding the normal case. A live reason
|
|
657
|
-
* pushed by a feature via `ctx.loadingReason` supersedes all of it. */
|
|
700
|
+
* omitted keeps the library default. It only ever appears on a SLOW start; a
|
|
701
|
+
* live reason pushed through `ctx.loadingReason` supersedes all of it. */
|
|
658
702
|
loadingMessages?: Partial<LoadingMessages>;
|
|
659
|
-
/** Observe or replace the
|
|
660
|
-
*
|
|
661
|
-
*
|
|
662
|
-
*
|
|
663
|
-
* this callback throws, the kernel logs that error and shows the built-in
|
|
664
|
-
* surface, so a reporting failure cannot leave the page blank. */
|
|
703
|
+
/** Observe or replace the fatal startup surface. Return true after rendering a
|
|
704
|
+
* replacement recovery surface into `failure.surface`; false or undefined
|
|
705
|
+
* keeps the built-in Reload surface. A throw here is logged and the built-in
|
|
706
|
+
* surface shown, so a reporting failure cannot leave the page blank. */
|
|
665
707
|
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type -- an observing handler returns nothing; only a literal `true` claims the recovery surface, so forcing `return undefined` on every observer is worse than the union
|
|
666
708
|
onFatalError?: (failure: TerminalStartupFailure) => boolean | void;
|
|
667
|
-
/** Called when the active session's process has ended and nothing is retrying
|
|
668
|
-
* the
|
|
669
|
-
*
|
|
670
|
-
*
|
|
671
|
-
*
|
|
672
|
-
*
|
|
673
|
-
* `TerminalHandle.reattach`). Everything the kernel does about the end — lower
|
|
674
|
-
* the loading overlay, surface the state, discard an unconfirmed restore —
|
|
675
|
-
* happens first and happens regardless, so a host that wires nothing loses
|
|
676
|
-
* nothing, and a callback that throws cannot take the banner down with it (the
|
|
677
|
-
* throw is logged and swallowed).
|
|
678
|
-
*
|
|
679
|
-
* Observation only: the recovery POLICY stays with the host, deliberately.
|
|
680
|
-
* Whether to reattach at all, how many times and how fast depends on what sits
|
|
681
|
-
* behind the endpoint, which this library does not know. */
|
|
709
|
+
/** Called when the active session's process has ended and nothing is retrying,
|
|
710
|
+
* the fact the connection banner renders as "Session ended". Observation
|
|
711
|
+
* only: everything the terminal does about the end happens first and
|
|
712
|
+
* regardless, and the recovery POLICY (see `TerminalHandle.reattach`) stays
|
|
713
|
+
* with the host, which alone knows what its endpoint yields on the next
|
|
714
|
+
* connect. A throw is logged and swallowed. */
|
|
682
715
|
onSessionEnded?: () => void;
|
|
683
|
-
/** Theme overrides: CSS custom properties set on the terminal root
|
|
684
|
-
*
|
|
685
|
-
*
|
|
686
|
-
*
|
|
687
|
-
* (css/00-tokens.css) — the "template"; these are the consumer's "settings"
|
|
688
|
-
* and override them for this instance.
|
|
689
|
-
*
|
|
690
|
-
* The supported keys are published as DATA, not prose: `PUBLIC_THEME_TOKENS`
|
|
691
|
-
* (exported at the package root and at "@cplieger/web-terminal-ui/style-contract").
|
|
692
|
-
* This type stays an open Record so a consumer may build its theme
|
|
693
|
-
* dynamically, which means nothing here rejects a key the library renamed or
|
|
694
|
-
* retired — the override would simply apply to nothing. Assert your keys
|
|
695
|
-
* against that list rather than trusting a comment; the library's own suite
|
|
696
|
-
* guarantees every token on it is both declared and read by a rule. */
|
|
716
|
+
/** Theme overrides: CSS custom properties set on the terminal root, over the
|
|
717
|
+
* defaults of css/00-tokens.css. The supported keys are published as DATA in
|
|
718
|
+
* `PUBLIC_THEME_TOKENS`; this type stays an open Record so a theme can be
|
|
719
|
+
* built dynamically, which means an unknown key applies to nothing. */
|
|
697
720
|
theme?: Readonly<Record<string, string>>;
|
|
721
|
+
/** Two panes side by side, driven from the tab chrome (a split button, the tab
|
|
722
|
+
* menu's snap items, tab drag and drop, and a focusable divider). Requires the
|
|
723
|
+
* built-in tabs feature (`tabs()` or a tabbed preset) in `features`; a terminal
|
|
724
|
+
* without it fails at `kernel-init`, as does a value other than true or false.
|
|
725
|
+
* With it the root becomes the shell root holding one pane root per pane; the
|
|
726
|
+
* DOM of a terminal built without it is unchanged. */
|
|
727
|
+
split?: boolean;
|
|
698
728
|
}
|
|
699
729
|
|
|
700
730
|
/** The handle createTerminal returns. Feature APIs are not materialized here
|
|
@@ -703,40 +733,26 @@ export interface CreateTerminalOptions {
|
|
|
703
733
|
export interface TerminalHandle {
|
|
704
734
|
/** Focus the terminal input (opens the soft keyboard on touch). */
|
|
705
735
|
focus(): void;
|
|
706
|
-
/** Send bytes to the active session through the
|
|
707
|
-
*
|
|
708
|
-
*
|
|
709
|
-
* input. The supported host path for "type this command" affordances
|
|
710
|
-
* (a run-in-shell button). No-op after destroy(). */
|
|
736
|
+
/** Send bytes to the active session through the sanitizing input funnel, the
|
|
737
|
+
* same path features use, so transforms apply and the view snaps to the
|
|
738
|
+
* bottom like typed input. No-op after destroy(). */
|
|
711
739
|
send(bytes: Uint8Array): void;
|
|
712
|
-
/** Reset the LOCAL display: drop the client-side scrollback and screen
|
|
713
|
-
*
|
|
714
|
-
* no keystroke — a host that wants a freshly drawn prompt sends one itself
|
|
715
|
-
* (e.g. Ctrl+L via send()). No-op after destroy(). */
|
|
716
|
-
reset(): void;
|
|
717
|
-
/** Attach again to whatever the server serves now, discarding this terminal's
|
|
718
|
-
* view of the session that is gone.
|
|
719
|
-
*
|
|
720
|
-
* For the one state the kernel cannot leave on its own: `ended`. The engine
|
|
721
|
-
* refuses to reconnect a definitively closed session, and it is right to on a
|
|
722
|
-
* per-session server, where reconnecting could only collect the same close
|
|
723
|
-
* again. A host whose endpoint hands out a NEW session on the next connect —
|
|
724
|
-
* one shared PTY that the server replaces once it is spent — is the only party
|
|
725
|
-
* that knows a reconnect is worth making, so it is the party that asks.
|
|
726
|
-
*
|
|
727
|
-
* Three steps, in this order, and none of them is a host's to perform: drop
|
|
728
|
-
* the local scrollback and screen (the old session's content, and a stale
|
|
729
|
-
* absolute-index base whose `haveThrough` would otherwise claim lines the new
|
|
730
|
-
* session has never reached), move the connection state off `ended` so the
|
|
731
|
-
* banner stops contradicting a blank screen, then reconnect.
|
|
732
|
-
*
|
|
733
|
-
* Reconnecting is ALL it does: it starts no process and asks the server for
|
|
734
|
-
* nothing. Whatever makes a new session exist is the host's own call to its
|
|
735
|
-
* own API, made before this one. Do not call it on a live session — that is a
|
|
736
|
-
* needless full replay, and it drops history the server may have evicted since.
|
|
740
|
+
/** Reset the LOCAL display: drop the client-side scrollback and screen.
|
|
741
|
+
* Injects no keystroke; a host that wants a fresh prompt sends one itself.
|
|
737
742
|
* No-op after destroy(). */
|
|
743
|
+
reset(): void;
|
|
744
|
+
/** Attach again to whatever the server serves now, for the one state the
|
|
745
|
+
* terminal cannot leave on its own: `ended`. Only a host whose endpoint hands
|
|
746
|
+
* out a NEW session on the next connect knows a reconnect is worth making, so
|
|
747
|
+
* it asks. Drops the local scrollback and screen (a stale `haveThrough` would
|
|
748
|
+
* claim lines the new session never reached), leaves `ended`, then reconnects;
|
|
749
|
+
* it starts no process. Not for a live session: a needless full replay. No-op
|
|
750
|
+
* after destroy(). */
|
|
738
751
|
reattach(): void;
|
|
739
752
|
/** Tear down every feature in reverse order, dispose all subscriptions, and
|
|
740
753
|
* release the kernel's DOM and engine wiring. */
|
|
741
754
|
destroy(): void;
|
|
755
|
+
/** The split controller; present only when `CreateTerminalOptions.split` was
|
|
756
|
+
* true. */
|
|
757
|
+
split?: SplitController;
|
|
742
758
|
}
|