@aitty/browser 0.1.2 → 0.2.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.
@@ -0,0 +1,221 @@
1
+ import { normalizeThemeName } from "./terminal-config.js";
2
+ //#region packages/browser/src/frontend/shell-controls.ts
3
+ const DEFAULT_SCROLL_RESTORE_PASSES = 8;
4
+ const STICKY_BOTTOM_SCROLL_RESTORE_PASSES = 16;
5
+ function installShellControls(options) {
6
+ const { captureScrollAnchor, doc, focusTerminal, getConfig, onInterrupt, onScrollSurfaceChange, onStickToBottomChange, resetConfig, restoreScrollAnchor, shell, updateConfig, windowObject } = options;
7
+ const fontSizeLabel = shell.querySelector("[data-terminal-font-size]");
8
+ const fontSizeDecreaseControl = shell.querySelector("[data-shell-control=\"font-size-decrease\"]");
9
+ const fontSizeIncreaseControl = shell.querySelector("[data-shell-control=\"font-size-increase\"]");
10
+ const fontSizeResetControl = shell.querySelector("[data-shell-control=\"font-size-reset\"]");
11
+ const viewportFullscreenControl = shell.querySelector("[data-shell-control=\"viewport-fullscreen\"]");
12
+ const systemFullscreenControl = shell.querySelector("[data-shell-control=\"system-fullscreen\"]");
13
+ const terminalContainer = shell.querySelector(".terminal-container");
14
+ const themeToggle = shell.querySelector("#theme-toggle");
15
+ let viewportFullscreen = false;
16
+ let pendingScrollAnchor = null;
17
+ let suppressNextControlClick = false;
18
+ let scrollRestoreFrame = null;
19
+ let scrollRestorePassesRemaining = 0;
20
+ const cancelScrollRestore = () => {
21
+ if (scrollRestoreFrame === null) {
22
+ scrollRestorePassesRemaining = 0;
23
+ return;
24
+ }
25
+ windowObject.cancelAnimationFrame(scrollRestoreFrame);
26
+ scrollRestoreFrame = null;
27
+ scrollRestorePassesRemaining = 0;
28
+ };
29
+ const scheduleScrollAnchorRestore = (scrollAnchor, passes = scrollAnchor.stickToBottom ? STICKY_BOTTOM_SCROLL_RESTORE_PASSES : DEFAULT_SCROLL_RESTORE_PASSES) => {
30
+ scrollRestorePassesRemaining = Math.max(scrollRestorePassesRemaining, passes);
31
+ if (scrollRestoreFrame !== null) return;
32
+ const runRestore = () => {
33
+ scrollRestoreFrame = windowObject.requestAnimationFrame(() => {
34
+ scrollRestoreFrame = null;
35
+ restoreScrollAnchor(scrollAnchor);
36
+ onScrollSurfaceChange?.();
37
+ scrollRestorePassesRemaining = Math.max(0, scrollRestorePassesRemaining - 1);
38
+ if (scrollRestorePassesRemaining > 0) runRestore();
39
+ });
40
+ };
41
+ runRestore();
42
+ };
43
+ const emitLayoutChange = () => {
44
+ windowObject.dispatchEvent(new windowObject.Event("resize"));
45
+ const scrollAnchor = pendingScrollAnchor;
46
+ pendingScrollAnchor = null;
47
+ onStickToBottomChange?.(scrollAnchor?.stickToBottom === true);
48
+ onScrollSurfaceChange?.();
49
+ if (!scrollAnchor) return;
50
+ windowObject.requestAnimationFrame(() => {
51
+ windowObject.requestAnimationFrame(() => {
52
+ scheduleScrollAnchorRestore(scrollAnchor);
53
+ });
54
+ });
55
+ };
56
+ const captureScrollTransition = () => {
57
+ cancelScrollRestore();
58
+ pendingScrollAnchor = captureScrollAnchor();
59
+ };
60
+ const syncFontSizeControls = () => {
61
+ const { fontSize, fontSizeRange: range } = getConfig().appearance;
62
+ if (fontSizeLabel) fontSizeLabel.textContent = `${fontSize}px`;
63
+ fontSizeDecreaseControl?.toggleAttribute("disabled", fontSize <= range.min);
64
+ fontSizeIncreaseControl?.toggleAttribute("disabled", fontSize >= range.max);
65
+ fontSizeResetControl?.setAttribute("aria-label", `Reset terminal font size (${fontSize}px)`);
66
+ fontSizeResetControl?.setAttribute("title", `Reset terminal font size (${fontSize}px)`);
67
+ };
68
+ const changeFontSize = (direction) => {
69
+ const { fontSize, fontSizeRange: range } = getConfig().appearance;
70
+ updateConfig({ appearance: { fontSize: fontSize + direction * range.step } });
71
+ syncFontSizeControls();
72
+ focusTerminal();
73
+ };
74
+ const resetFontSize = () => {
75
+ updateConfig({ appearance: { fontSize: resetConfig.appearance.fontSize } });
76
+ syncFontSizeControls();
77
+ focusTerminal();
78
+ };
79
+ const syncViewportFullscreenState = () => {
80
+ shell.dataset.viewportFullscreen = viewportFullscreen ? "true" : "false";
81
+ viewportFullscreenControl?.setAttribute("aria-pressed", viewportFullscreen ? "true" : "false");
82
+ viewportFullscreenControl?.setAttribute("aria-label", viewportFullscreen ? "Exit browser fullscreen" : "Enter browser fullscreen");
83
+ viewportFullscreenControl?.setAttribute("title", viewportFullscreen ? "Exit browser fullscreen" : "Enter browser fullscreen");
84
+ emitLayoutChange();
85
+ };
86
+ const syncSystemFullscreenState = () => {
87
+ const fullscreen = doc.fullscreenElement === shell;
88
+ shell.dataset.fullscreen = fullscreen ? "true" : "false";
89
+ systemFullscreenControl?.setAttribute("aria-pressed", fullscreen ? "true" : "false");
90
+ systemFullscreenControl?.setAttribute("aria-label", fullscreen ? "Exit system fullscreen" : "Enter system fullscreen");
91
+ systemFullscreenControl?.setAttribute("title", fullscreen ? "Exit system fullscreen" : "Enter system fullscreen");
92
+ emitLayoutChange();
93
+ };
94
+ const toggleViewportFullscreen = () => {
95
+ captureScrollTransition();
96
+ viewportFullscreen = !viewportFullscreen;
97
+ syncViewportFullscreenState();
98
+ };
99
+ const toggleSystemFullscreen = async () => {
100
+ if (doc.fullscreenElement === shell) {
101
+ captureScrollTransition();
102
+ if (doc.fullscreenElement && typeof doc.exitFullscreen === "function") await doc.exitFullscreen();
103
+ else syncSystemFullscreenState();
104
+ return;
105
+ }
106
+ if (typeof shell.requestFullscreen !== "function") {
107
+ toggleViewportFullscreen();
108
+ return;
109
+ }
110
+ captureScrollTransition();
111
+ try {
112
+ await shell.requestFullscreen();
113
+ } catch {
114
+ pendingScrollAnchor = null;
115
+ toggleViewportFullscreen();
116
+ }
117
+ };
118
+ const toggleTheme = () => {
119
+ const nextTheme = (getConfig().appearance.theme ?? normalizeThemeName(shell.dataset.theme)) === "light" ? "dark" : "light";
120
+ shell.dataset.themeOverride = "true";
121
+ updateConfig({ appearance: { theme: nextTheme } });
122
+ focusTerminal();
123
+ };
124
+ const toggleCollapse = () => {
125
+ const isCollapsed = terminalContainer?.dataset.collapsed === "true";
126
+ if (terminalContainer) {
127
+ terminalContainer.dataset.collapsed = isCollapsed ? "false" : "true";
128
+ emitLayoutChange();
129
+ }
130
+ };
131
+ const runAction = (action) => {
132
+ if (action === "close") {
133
+ onInterrupt();
134
+ focusTerminal();
135
+ return;
136
+ }
137
+ if (action === "viewport-fullscreen") {
138
+ toggleViewportFullscreen();
139
+ return;
140
+ }
141
+ if (action === "system-fullscreen") {
142
+ toggleSystemFullscreen();
143
+ return;
144
+ }
145
+ if (action === "collapse") {
146
+ toggleCollapse();
147
+ return;
148
+ }
149
+ if (action === "font-size-decrease") {
150
+ changeFontSize(-1);
151
+ return;
152
+ }
153
+ if (action === "font-size-increase") {
154
+ changeFontSize(1);
155
+ return;
156
+ }
157
+ if (action === "font-size-reset") resetFontSize();
158
+ };
159
+ const handleClick = (event) => {
160
+ const target = event.target instanceof Element ? event.target : null;
161
+ const control = target?.closest("[data-shell-control]");
162
+ if (control && shell.contains(control)) {
163
+ event.preventDefault();
164
+ if (suppressNextControlClick) {
165
+ suppressNextControlClick = false;
166
+ return;
167
+ }
168
+ runAction(control.dataset.shellControl);
169
+ return;
170
+ }
171
+ if (themeToggle && (themeToggle === target || themeToggle.contains(target))) {
172
+ event.preventDefault();
173
+ toggleTheme();
174
+ }
175
+ };
176
+ const handlePointerDown = (event) => {
177
+ const control = (event.target instanceof Element ? event.target : null)?.closest("[data-shell-control]");
178
+ if (!control || !shell.contains(control) || control.dataset.shellControl !== "viewport-fullscreen" || !shouldUsePointerDownViewportFullscreenToggle(windowObject)) return;
179
+ event.preventDefault();
180
+ suppressNextControlClick = true;
181
+ toggleViewportFullscreen();
182
+ };
183
+ const handleExternalControl = (event) => {
184
+ runAction("detail" in event ? event.detail : void 0);
185
+ };
186
+ const handleFullscreenChange = () => {
187
+ syncSystemFullscreenState();
188
+ };
189
+ const systemThemeMediaQuery = windowObject.matchMedia("(prefers-color-scheme: dark)");
190
+ const handleSystemThemeChange = (event) => {
191
+ if (!shell.dataset.themeOverride) updateConfig({ appearance: { theme: event.matches ? "dark" : "light" } });
192
+ };
193
+ shell.addEventListener("pointerdown", handlePointerDown, true);
194
+ shell.addEventListener("click", handleClick);
195
+ windowObject.addEventListener("aitty:control", handleExternalControl);
196
+ doc.addEventListener("fullscreenchange", handleFullscreenChange);
197
+ if (typeof systemThemeMediaQuery.addEventListener === "function") systemThemeMediaQuery.addEventListener("change", handleSystemThemeChange);
198
+ else systemThemeMediaQuery.addListener(handleSystemThemeChange);
199
+ if (!shell.dataset.theme) handleSystemThemeChange(systemThemeMediaQuery);
200
+ syncViewportFullscreenState();
201
+ syncSystemFullscreenState();
202
+ syncFontSizeControls();
203
+ return () => {
204
+ cancelScrollRestore();
205
+ shell.removeEventListener("pointerdown", handlePointerDown, true);
206
+ shell.removeEventListener("click", handleClick);
207
+ windowObject.removeEventListener("aitty:control", handleExternalControl);
208
+ doc.removeEventListener("fullscreenchange", handleFullscreenChange);
209
+ if (typeof systemThemeMediaQuery.removeEventListener === "function") systemThemeMediaQuery.removeEventListener("change", handleSystemThemeChange);
210
+ else systemThemeMediaQuery.removeListener(handleSystemThemeChange);
211
+ };
212
+ }
213
+ function shouldUsePointerDownViewportFullscreenToggle(windowObject) {
214
+ const navigatorLike = windowObject.navigator;
215
+ const platform = navigatorLike?.platform ?? "";
216
+ const userAgent = navigatorLike?.userAgent ?? "";
217
+ const maxTouchPoints = navigatorLike?.maxTouchPoints ?? 0;
218
+ return (/iP(?:hone|ad|od)/.test(platform) || /iP(?:hone|ad|od)/.test(userAgent) || platform === "MacIntel" && maxTouchPoints > 1) && windowObject.matchMedia?.("(pointer: coarse)").matches === true;
219
+ }
220
+ //#endregion
221
+ export { installShellControls };
@@ -1,26 +1,40 @@
1
1
  import { BrowserTerminalBridge, BrowserTerminalRenderOptions } from "./browser-terminal-renderer.js";
2
- import { TerminalKeyResolver } from "./terminal-input-policies.js";
3
- import { WTermOptions } from "@wterm/dom";
4
- import { AittyRuntimeKind, AittyTerminalConnectionState, AittyTerminalLoadingPresentation, AittyTerminalOutputState, AittyTerminalSessionPresentationState, AittyTerminalStatusSnapshot, AittyTheme } from "@aitty/protocol";
2
+ import { TerminalAppearanceConfig, TerminalBehaviorConfig, TerminalConfig, TerminalConfigListener, TerminalConfigPatch, TerminalFontSizeRange, TerminalResolvedAppearanceConfig, TerminalResolvedBehaviorConfig, TerminalResolvedConfig, TerminalTheme, TerminalThemeTarget } from "./terminal-config.js";
3
+ import { AittyRuntimeKind, AittyTerminalConnectionState, AittyTerminalLoadingPresentation, AittyTerminalOutputState, AittyTerminalSessionPresentationState, AittyTerminalStatusSnapshot } from "@aitty/protocol";
5
4
 
6
- //#region src/frontend/terminal-app.d.ts
5
+ //#region packages/browser/src/frontend/terminal-app.d.ts
7
6
  type RuntimeKind = AittyRuntimeKind;
8
- type TerminalTheme = AittyTheme;
9
7
  type TerminalConnectionState = AittyTerminalConnectionState;
10
8
  type TerminalOutputState = AittyTerminalOutputState;
11
9
  type TerminalSessionPresentationState = AittyTerminalSessionPresentationState;
12
10
  type BinaryWriteTarget = {
13
11
  write(data: Uint8Array): void;
14
12
  };
13
+ type TimeoutHandle = ReturnType<typeof setTimeout> | number;
15
14
  type FrameScheduler = {
16
15
  cancelFrame?: (handle: number) => void;
16
+ now?: () => number;
17
17
  requestFrame?: (callback: FrameRequestCallback) => number;
18
+ clearTimeout?: (handle: TimeoutHandle) => void;
19
+ setTimeout?: (callback: () => void, delayMs: number) => TimeoutHandle;
18
20
  };
19
21
  type BufferedTerminalWriter = {
20
22
  destroy(): void;
21
23
  discardPending(): void;
22
24
  enqueue(chunk: Uint8Array): void;
23
25
  };
26
+ type BufferedTerminalWriterOptions = {
27
+ maxBytesPerFrame?: number;
28
+ minFrameIntervalMs?: number;
29
+ resolveMinFrameIntervalMs?: (payload: {
30
+ pendingByteLength: number;
31
+ }) => number;
32
+ onDrain?: () => void;
33
+ onFlush?: (payload: {
34
+ pendingByteLength: number;
35
+ wroteByteLength: number;
36
+ }) => void;
37
+ };
24
38
  type TerminalLoadingPresentation = AittyTerminalLoadingPresentation;
25
39
  type TerminalStatusSnapshot = AittyTerminalStatusSnapshot;
26
40
  type TerminalStatusListener = (status: TerminalStatusSnapshot) => void;
@@ -76,68 +90,116 @@ type TerminalTransport = {
76
90
  send(data: string | Uint8Array): void;
77
91
  sendControl?(data: string): void;
78
92
  };
93
+ type BrowserWTermOptions = {
94
+ autoResize?: boolean;
95
+ cols?: number;
96
+ cursorBlink?: boolean;
97
+ onData?: (data: string) => void;
98
+ onResize?: (cols: number, rows: number) => void;
99
+ onTitle?: (title: string) => void;
100
+ rows?: number;
101
+ wasmUrl?: string;
102
+ };
79
103
  type MountTerminalDependencies = {
80
- createTerm?: (element: HTMLElement, options: WTermOptions) => BrowserWTerm;
104
+ config?: TerminalConfig;
105
+ createTerm?: (element: HTMLElement, options: BrowserWTermOptions) => BrowserWTerm;
81
106
  createTransport?: (handlers: TransportHandlers) => TerminalTransport;
82
107
  elements?: MountTerminalElements;
83
108
  frameScheduler?: FrameScheduler;
84
- input?: {
85
- resolveKey?: TerminalKeyResolver;
86
- };
109
+ onConfigChange?: TerminalConfigListener;
87
110
  onStatusChange?: TerminalStatusListener;
88
111
  reconnectDelayMs?: number;
89
112
  resolveRuntimeKind?: (doc: Document, shell: HTMLElement) => RuntimeKind | Promise<RuntimeKind>;
90
113
  scroll?: TerminalScrollOptions;
114
+ shellControls?: boolean;
91
115
  src?: string | URL;
92
- theme?: TerminalTheme;
93
- transcriptArchiveOptions?: {
94
- scrollbackLimit?: number;
95
- };
96
116
  };
97
117
  type MountedTerminalApp = {
98
118
  destroy(): void;
119
+ getConfig(): TerminalResolvedConfig;
99
120
  getStatus(): TerminalStatusSnapshot;
100
- getTheme(): string | undefined;
101
- setTheme(theme: TerminalTheme | undefined): void;
102
121
  term: BrowserWTerm;
103
122
  transport: TerminalTransport;
123
+ updateConfig(config: TerminalConfigPatch): TerminalResolvedConfig;
104
124
  };
105
125
  type TerminalRendererLike = {
106
126
  cols?: number;
127
+ getRenderedScrollbackCount?: () => number;
107
128
  render?(bridge: BrowserTerminalBridge, options?: BrowserTerminalRenderOptions): void;
108
129
  rows?: number;
109
130
  setup?(cols: number, rows: number): void;
110
131
  };
132
+ type BrowserWTermWriteOptions = {
133
+ scheduleRender?: boolean;
134
+ };
111
135
  type BrowserWTerm = {
112
136
  _container?: HTMLElement;
113
137
  _measureCharSize?: () => TerminalCellMetrics;
114
138
  bridge?: BrowserTerminalBridge | null;
139
+ clearSelection?: () => void;
115
140
  cols: number;
116
141
  destroy(): void;
117
142
  element: HTMLElement;
118
143
  focus(): void;
144
+ getSelectionText?: () => string;
145
+ hasSelection?: () => boolean;
119
146
  init(): Promise<unknown>;
120
147
  input?: {
121
148
  destroy?: () => void;
122
149
  focus?: () => void;
150
+ setImeAnchor?: (anchor: HTMLElement | null) => void;
123
151
  textarea?: HTMLTextAreaElement | null;
124
152
  };
153
+ renderNow?: (options?: BrowserTerminalRenderOptions) => void;
125
154
  renderer?: TerminalRendererLike;
126
155
  resize(cols: number, rows: number): void;
127
156
  rows: number;
128
- write?(data: string | Uint8Array): void;
157
+ write?(data: string | Uint8Array, options?: BrowserWTermWriteOptions): void;
129
158
  };
130
159
  type TerminalCellMetrics = {
131
160
  charWidth: number;
132
161
  rowHeight: number;
133
162
  };
163
+ type BrowserWindow = Window & typeof globalThis;
164
+ type MobileComposerControl = "arrow-down" | "arrow-left" | "arrow-right" | "arrow-up" | "enter" | "esc" | "tab";
165
+ declare const MOBILE_COMPOSER_PRIMARY_CONTROLS: readonly MobileComposerControl[];
166
+ type BrowserViewport = {
167
+ browserChromeInsetBottom: number;
168
+ height: number;
169
+ keyboardInsetBottom: number;
170
+ offsetTop: number;
171
+ safeHeight: number;
172
+ width: number;
173
+ };
174
+ type MobileFocusProxyPosition = {
175
+ anchor: "mobile-focus-proxy";
176
+ height: number;
177
+ left: number;
178
+ top: number;
179
+ width: number;
180
+ };
181
+ type MobileFocusProxyPointerState = {
182
+ keyboardOpen?: boolean;
183
+ textareaFocused?: boolean;
184
+ };
134
185
  /**
135
186
  * Batches raw PTY bytes into animation frames without splitting UTF-8 codepoints or
136
187
  * incomplete ANSI sequences. This is the only throttling layer in the browser path.
137
188
  */
138
- declare function createBufferedTerminalWriter(target: BinaryWriteTarget, scheduler?: FrameScheduler, options?: {
139
- maxBytesPerFrame?: number;
140
- }): BufferedTerminalWriter;
189
+ declare function createBufferedTerminalWriter(target: BinaryWriteTarget, scheduler?: FrameScheduler, options?: BufferedTerminalWriterOptions): BufferedTerminalWriter;
190
+ declare function resolveBrowserViewport(windowObject: BrowserWindow): BrowserViewport;
191
+ declare function resolveMobileFocusProxyPosition(windowObject: BrowserWindow): MobileFocusProxyPosition;
192
+ declare function shouldEnableMobileFocusProxyPointerEvents(windowObject: BrowserWindow, state?: MobileFocusProxyPointerState): boolean;
193
+ declare function isMobileLiveInputTapTarget(terminalRoot: HTMLElement, windowObject: BrowserWindow, clientX: number, clientY: number): boolean;
194
+ declare function resolveMobileKeyboardPromptAlignDelays(): number[];
195
+ declare function resolveTerminalOutputFrameIntervalMs({
196
+ pendingByteLength,
197
+ sustainedPressure
198
+ }: {
199
+ pendingByteLength: number;
200
+ sustainedPressure?: boolean;
201
+ }): number;
202
+ declare function resolveMobileComposerExpandedListHeight(controlCount?: number): number;
141
203
  declare function mountTerminalApp(doc?: Document, dependencies?: MountTerminalDependencies): Promise<MountedTerminalApp>;
142
204
  interface MountAittyOptions extends MountTerminalDependencies {
143
205
  document?: Document;
@@ -152,5 +214,7 @@ interface MountAittyContainerOptions extends Omit<MountAittyOptions, "elements"
152
214
  declare function mountAitty(options?: MountAittyOptions): Promise<MountedTerminalApp>;
153
215
  declare function mountAitty(container: HTMLElement | string, options?: MountAittyContainerOptions): Promise<MountedTerminalApp>;
154
216
  declare function defineAittyTerminalElement(registry?: CustomElementRegistry | undefined): void;
217
+ declare function shouldFollowLiveOutputOnUserInputData(data: string | Uint8Array): boolean;
218
+ declare function resolveMobileComposerControlSequence(control: MobileComposerControl): string;
155
219
  //#endregion
156
- export { BinaryWriteTarget, BufferedTerminalWriter, FrameScheduler, MountAittyContainerOptions, MountAittyOptions, MountTerminalDependencies, MountTerminalElements, MountedTerminalApp, TerminalConnectionState, TerminalLoadingPresentation, TerminalOutputState, TerminalScrollAdapter, TerminalScrollAdapterContext, TerminalScrollAdapterFactory, TerminalScrollMetrics, TerminalScrollOptions, TerminalSessionPresentationState, TerminalStatusListener, TerminalStatusSnapshot, TerminalTheme, TerminalTransport, TransportCloseEvent, TransportHandlers, createBufferedTerminalWriter, defineAittyTerminalElement, mountAitty, mountTerminalApp };
220
+ export { BinaryWriteTarget, BrowserViewport, BrowserWTermOptions, BufferedTerminalWriter, FrameScheduler, MOBILE_COMPOSER_PRIMARY_CONTROLS, MobileFocusProxyPointerState, MobileFocusProxyPosition, MountAittyContainerOptions, MountAittyOptions, MountTerminalDependencies, MountTerminalElements, MountedTerminalApp, type TerminalAppearanceConfig, type TerminalBehaviorConfig, type TerminalConfig, type TerminalConfigListener, type TerminalConfigPatch, TerminalConnectionState, type TerminalFontSizeRange, TerminalLoadingPresentation, TerminalOutputState, type TerminalResolvedAppearanceConfig, type TerminalResolvedBehaviorConfig, type TerminalResolvedConfig, TerminalScrollAdapter, TerminalScrollAdapterContext, TerminalScrollAdapterFactory, TerminalScrollMetrics, TerminalScrollOptions, TerminalSessionPresentationState, TerminalStatusListener, TerminalStatusSnapshot, type TerminalTheme, type TerminalThemeTarget, TerminalTransport, TransportCloseEvent, TransportHandlers, createBufferedTerminalWriter, defineAittyTerminalElement, isMobileLiveInputTapTarget, mountAitty, mountTerminalApp, resolveBrowserViewport, resolveMobileComposerControlSequence, resolveMobileComposerExpandedListHeight, resolveMobileFocusProxyPosition, resolveMobileKeyboardPromptAlignDelays, resolveTerminalOutputFrameIntervalMs, shouldEnableMobileFocusProxyPointerEvents, shouldFollowLiveOutputOnUserInputData };