green-screen-react 0.4.0 → 1.0.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/dist/index.mjs CHANGED
@@ -61,15 +61,39 @@ var RestAdapter = class {
61
61
  };
62
62
 
63
63
  // src/adapters/WebSocketAdapter.ts
64
- var WebSocketAdapter = class {
64
+ var WebSocketAdapter = class _WebSocketAdapter {
65
65
  constructor(options = {}) {
66
66
  this.ws = null;
67
67
  this.screen = null;
68
68
  this.status = { connected: false, status: "disconnected" };
69
- this.pendingResolvers = /* @__PURE__ */ new Map();
69
+ this.pendingScreenResolver = null;
70
+ this.pendingConnectResolver = null;
71
+ this.connectingPromise = null;
70
72
  this.screenListeners = /* @__PURE__ */ new Set();
71
73
  this.statusListeners = /* @__PURE__ */ new Set();
72
- this.workerUrl = (options.workerUrl || "http://localhost:3001").replace(/\/+$/, "");
74
+ this._sessionId = null;
75
+ this.workerUrl = (options.workerUrl || _WebSocketAdapter.detectEnvUrl() || "http://localhost:3001").replace(/\/+$/, "");
76
+ }
77
+ static detectEnvUrl() {
78
+ try {
79
+ if (typeof import.meta !== "undefined" && import.meta.env) {
80
+ const env = import.meta.env;
81
+ return env.VITE_GREEN_SCREEN_URL || env.VITE_WORKER_URL || void 0;
82
+ }
83
+ } catch {
84
+ }
85
+ try {
86
+ const p = typeof globalThis !== "undefined" && globalThis.process;
87
+ if (p && p.env) {
88
+ return p.env.NEXT_PUBLIC_GREEN_SCREEN_URL || p.env.REACT_APP_GREEN_SCREEN_URL || void 0;
89
+ }
90
+ } catch {
91
+ }
92
+ return void 0;
93
+ }
94
+ /** The proxy-side session ID (available after connect or reattach) */
95
+ get sessionId() {
96
+ return this._sessionId;
73
97
  }
74
98
  /** Subscribe to real-time screen updates */
75
99
  onScreen(listener) {
@@ -100,24 +124,13 @@ var WebSocketAdapter = class {
100
124
  }
101
125
  return new Promise((resolve) => {
102
126
  const timeout = setTimeout(() => {
127
+ this.pendingConnectResolver = null;
103
128
  resolve({ success: false, error: "Connection timeout" });
104
129
  }, 3e4);
105
- const onMessage = (event) => {
106
- try {
107
- const msg = JSON.parse(event.data);
108
- if (msg.type === "connected") {
109
- clearTimeout(timeout);
110
- this.ws?.removeEventListener("message", onMessage);
111
- resolve({ success: true });
112
- } else if (msg.type === "error") {
113
- clearTimeout(timeout);
114
- this.ws?.removeEventListener("message", onMessage);
115
- resolve({ success: false, error: msg.message });
116
- }
117
- } catch {
118
- }
130
+ this.pendingConnectResolver = (result) => {
131
+ clearTimeout(timeout);
132
+ resolve(result);
119
133
  };
120
- this.ws?.addEventListener("message", onMessage);
121
134
  this.wsSend({
122
135
  type: "connect",
123
136
  host: config.host,
@@ -128,9 +141,29 @@ var WebSocketAdapter = class {
128
141
  });
129
142
  });
130
143
  }
144
+ /**
145
+ * Reattach to an existing proxy session (e.g. after page reload).
146
+ * The proxy keeps the TCP connection alive; this just reconnects the
147
+ * WebSocket and receives the current screen.
148
+ */
149
+ async reattach(sessionId) {
150
+ await this.ensureWebSocket();
151
+ return new Promise((resolve) => {
152
+ const timeout = setTimeout(() => {
153
+ this.pendingConnectResolver = null;
154
+ resolve({ success: false, error: "Reattach timeout" });
155
+ }, 1e4);
156
+ this.pendingConnectResolver = (result) => {
157
+ clearTimeout(timeout);
158
+ resolve(result);
159
+ };
160
+ this.wsSend({ type: "reattach", sessionId });
161
+ });
162
+ }
131
163
  async disconnect() {
132
164
  this.wsSend({ type: "disconnect" });
133
165
  this.status = { connected: false, status: "disconnected" };
166
+ this._sessionId = null;
134
167
  if (this.ws) {
135
168
  this.ws.close();
136
169
  this.ws = null;
@@ -140,7 +173,7 @@ var WebSocketAdapter = class {
140
173
  async reconnect() {
141
174
  return { success: false, error: "Use disconnect() then connect() instead" };
142
175
  }
143
- /** Close the WebSocket without sending disconnect */
176
+ /** Close the WebSocket without sending disconnect (session stays alive on proxy) */
144
177
  dispose() {
145
178
  if (this.ws) {
146
179
  this.ws.close();
@@ -149,7 +182,8 @@ var WebSocketAdapter = class {
149
182
  }
150
183
  async ensureWebSocket() {
151
184
  if (this.ws && this.ws.readyState === WebSocket.OPEN) return;
152
- return new Promise((resolve, reject) => {
185
+ if (this.connectingPromise) return this.connectingPromise;
186
+ this.connectingPromise = new Promise((resolve, reject) => {
153
187
  const wsUrl = this.workerUrl.replace(/^http/, "ws") + "/ws";
154
188
  this.ws = new WebSocket(wsUrl);
155
189
  this.ws.onopen = () => resolve();
@@ -165,39 +199,61 @@ var WebSocketAdapter = class {
165
199
  this.status = { connected: false, status: "disconnected" };
166
200
  for (const listener of this.statusListeners) listener(this.status);
167
201
  };
202
+ }).finally(() => {
203
+ this.connectingPromise = null;
168
204
  });
205
+ return this.connectingPromise;
169
206
  }
170
207
  handleMessage(msg) {
171
208
  switch (msg.type) {
172
- case "screen":
209
+ case "screen": {
173
210
  this.screen = msg.data;
174
211
  for (const listener of this.screenListeners) listener(msg.data);
175
- const screenResolver = this.pendingResolvers.get("screen");
176
- if (screenResolver) {
177
- this.pendingResolvers.delete("screen");
178
- screenResolver(msg.data);
212
+ if (this.pendingScreenResolver) {
213
+ const resolver = this.pendingScreenResolver;
214
+ this.pendingScreenResolver = null;
215
+ resolver(msg.data);
179
216
  }
180
217
  break;
218
+ }
181
219
  case "status":
182
220
  this.status = msg.data;
183
221
  for (const listener of this.statusListeners) listener(msg.data);
184
222
  break;
185
- case "error":
186
- const errorResolver = this.pendingResolvers.get("screen");
187
- if (errorResolver) {
188
- this.pendingResolvers.delete("screen");
189
- errorResolver(null);
223
+ case "connected":
224
+ this._sessionId = msg.sessionId ?? null;
225
+ if (this.pendingConnectResolver) {
226
+ const resolver = this.pendingConnectResolver;
227
+ this.pendingConnectResolver = null;
228
+ resolver({ success: true });
229
+ }
230
+ break;
231
+ case "error": {
232
+ if (this.pendingConnectResolver) {
233
+ const resolver = this.pendingConnectResolver;
234
+ this.pendingConnectResolver = null;
235
+ resolver({ success: false, error: msg.message });
236
+ } else if (this.pendingScreenResolver) {
237
+ const resolver = this.pendingScreenResolver;
238
+ this.pendingScreenResolver = null;
239
+ resolver(null);
190
240
  }
191
241
  break;
242
+ }
192
243
  }
193
244
  }
194
245
  sendAndWaitForScreen(msg) {
195
246
  return new Promise((resolve) => {
247
+ if (this.pendingScreenResolver) {
248
+ const old = this.pendingScreenResolver;
249
+ this.pendingScreenResolver = null;
250
+ old(this.screen);
251
+ }
196
252
  const timeout = setTimeout(() => {
197
- this.pendingResolvers.delete("screen");
253
+ this.pendingScreenResolver = null;
198
254
  resolve({ success: true, ...this.screenToResult() });
199
255
  }, 5e3);
200
- this.pendingResolvers.set("screen", (screen) => {
256
+ this.pendingScreenResolver = (screen) => {
201
257
  clearTimeout(timeout);
202
258
  if (screen) {
203
259
  resolve({
@@ -210,7 +266,7 @@ var WebSocketAdapter = class {
210
266
  } else {
211
267
  resolve({ success: false, error: "No screen data received" });
212
268
  }
213
- });
269
+ };
214
270
  this.wsSend(msg);
215
271
  });
216
272
  }
@@ -841,20 +897,23 @@ var PROTOCOL_OPTIONS = [
841
897
  { value: "vt", label: "VT220" },
842
898
  { value: "hp6530", label: "HP 6530 (NonStop)" }
843
899
  ];
844
- function InlineSignIn({ defaultProtocol, loading, error, onConnect }) {
900
+ function InlineSignIn({ defaultProtocol, loading: externalLoading, error, onConnect }) {
845
901
  const [host, setHost] = useState4("");
846
902
  const [port, setPort] = useState4("");
847
903
  const [selectedProtocol, setSelectedProtocol] = useState4(defaultProtocol);
848
904
  const [username, setUsername] = useState4("");
849
905
  const [password, setPassword] = useState4("");
906
+ const [submitted, setSubmitted] = useState4(false);
907
+ const loading = externalLoading || submitted;
850
908
  const handleSubmit = (e) => {
851
909
  e.preventDefault();
910
+ setSubmitted(true);
852
911
  onConnect({
853
912
  host,
854
- port: port ? parseInt(port, 10) : void 0,
913
+ port: port ? parseInt(port, 10) : 23,
855
914
  protocol: selectedProtocol,
856
- username,
857
- password
915
+ ...username.trim() ? { username: username.trim() } : {},
916
+ ...password ? { password } : {}
858
917
  });
859
918
  };
860
919
  const inputStyle = {
@@ -877,6 +936,16 @@ function InlineSignIn({ defaultProtocol, loading, error, onConnect }) {
877
936
  color: "var(--gs-muted, #94a3b8)",
878
937
  fontFamily: "var(--gs-font)"
879
938
  };
939
+ if (loading) {
940
+ return /* @__PURE__ */ jsxs3("div", { className: "gs-signin", style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: "12px" }, children: [
941
+ /* @__PURE__ */ jsx3(RefreshIcon, { size: 28, className: "gs-spin" }),
942
+ /* @__PURE__ */ jsxs3("div", { style: { fontSize: "11px", letterSpacing: "0.15em", textTransform: "uppercase", color: "var(--gs-muted)", fontFamily: "var(--gs-font)" }, children: [
943
+ "Connecting to ",
944
+ host || "host",
945
+ "..."
946
+ ] })
947
+ ] });
948
+ }
880
949
  return /* @__PURE__ */ jsxs3("form", { onSubmit: handleSubmit, className: "gs-signin", children: [
881
950
  /* @__PURE__ */ jsxs3("div", { style: { textAlign: "center", marginBottom: "16px" }, children: [
882
951
  /* @__PURE__ */ jsx3(TerminalIcon, { size: 28 }),
@@ -884,7 +953,10 @@ function InlineSignIn({ defaultProtocol, loading, error, onConnect }) {
884
953
  ] }),
885
954
  /* @__PURE__ */ jsxs3("div", { className: "gs-signin-row", children: [
886
955
  /* @__PURE__ */ jsxs3("div", { style: { flex: 1 }, children: [
887
- /* @__PURE__ */ jsx3("label", { style: labelStyle, children: "Host" }),
956
+ /* @__PURE__ */ jsxs3("label", { style: labelStyle, children: [
957
+ "Host ",
958
+ /* @__PURE__ */ jsx3("span", { style: { color: "#ef4444" }, children: "*" })
959
+ ] }),
888
960
  /* @__PURE__ */ jsx3("input", { style: inputStyle, value: host, onChange: (e) => setHost(e.target.value), placeholder: "192.168.1.100", required: true, autoFocus: true })
889
961
  ] }),
890
962
  /* @__PURE__ */ jsxs3("div", { style: { width: "72px" }, children: [
@@ -893,22 +965,25 @@ function InlineSignIn({ defaultProtocol, loading, error, onConnect }) {
893
965
  ] })
894
966
  ] }),
895
967
  /* @__PURE__ */ jsxs3("div", { children: [
896
- /* @__PURE__ */ jsx3("label", { style: labelStyle, children: "Protocol" }),
968
+ /* @__PURE__ */ jsxs3("label", { style: labelStyle, children: [
969
+ "Protocol ",
970
+ /* @__PURE__ */ jsx3("span", { style: { color: "#ef4444" }, children: "*" })
971
+ ] }),
897
972
  /* @__PURE__ */ jsx3("select", { style: { ...inputStyle, appearance: "none" }, value: selectedProtocol, onChange: (e) => setSelectedProtocol(e.target.value), children: PROTOCOL_OPTIONS.map((o) => /* @__PURE__ */ jsx3("option", { value: o.value, children: o.label }, o.value)) })
898
973
  ] }),
899
974
  /* @__PURE__ */ jsxs3("div", { children: [
900
975
  /* @__PURE__ */ jsx3("label", { style: labelStyle, children: "Username" }),
901
- /* @__PURE__ */ jsx3("input", { style: inputStyle, value: username, onChange: (e) => setUsername(e.target.value), required: true, autoComplete: "username" })
976
+ /* @__PURE__ */ jsx3("input", { style: inputStyle, value: username, onChange: (e) => setUsername(e.target.value), autoComplete: "username" })
902
977
  ] }),
903
978
  /* @__PURE__ */ jsxs3("div", { children: [
904
979
  /* @__PURE__ */ jsx3("label", { style: labelStyle, children: "Password" }),
905
- /* @__PURE__ */ jsx3("input", { style: inputStyle, type: "password", value: password, onChange: (e) => setPassword(e.target.value), required: true, autoComplete: "current-password" })
980
+ /* @__PURE__ */ jsx3("input", { style: inputStyle, type: "password", value: password, onChange: (e) => setPassword(e.target.value), autoComplete: "current-password" })
906
981
  ] }),
907
982
  error && /* @__PURE__ */ jsxs3("div", { style: { color: "#FF6B00", fontSize: "11px", fontFamily: "var(--gs-font)", display: "flex", alignItems: "center", gap: "6px" }, children: [
908
983
  /* @__PURE__ */ jsx3(AlertTriangleIcon, { size: 12 }),
909
984
  /* @__PURE__ */ jsx3("span", { children: error })
910
985
  ] }),
911
- /* @__PURE__ */ jsx3("button", { type: "submit", disabled: loading || !host || !username || !password, className: "gs-signin-btn", children: loading ? "Connecting..." : "Connect" })
986
+ /* @__PURE__ */ jsx3("button", { type: "submit", disabled: !host, className: "gs-signin-btn", children: "Connect" })
912
987
  ] });
913
988
  }
914
989
 
@@ -927,6 +1002,7 @@ var noopAdapter = {
927
1002
  function GreenScreenTerminal({
928
1003
  adapter: externalAdapter,
929
1004
  baseUrl,
1005
+ workerUrl,
930
1006
  protocol,
931
1007
  protocolProfile: customProfile,
932
1008
  screenData: externalScreenData,
@@ -942,6 +1018,8 @@ function GreenScreenTerminal({
942
1018
  inlineSignIn = true,
943
1019
  defaultProtocol: signInDefaultProtocol,
944
1020
  onSignIn,
1021
+ autoSignedIn,
1022
+ autoFocusDisabled = false,
945
1023
  bootLoader,
946
1024
  headerRight,
947
1025
  overlay,
@@ -957,11 +1035,16 @@ function GreenScreenTerminal({
957
1035
  () => baseUrl ? new RestAdapter({ baseUrl }) : null,
958
1036
  [baseUrl]
959
1037
  );
1038
+ const workerUrlAdapter = useMemo2(
1039
+ () => workerUrl ? new WebSocketAdapter({ workerUrl }) : null,
1040
+ [workerUrl]
1041
+ );
960
1042
  const defaultWsAdapter = useMemo2(
961
- () => !externalAdapter && !baseUrl ? new WebSocketAdapter() : null,
962
- [externalAdapter, baseUrl]
1043
+ () => !externalAdapter && !baseUrl && !workerUrl ? new WebSocketAdapter() : null,
1044
+ [externalAdapter, baseUrl, workerUrl]
963
1045
  );
964
- const adapter = externalAdapter ?? baseUrlAdapter ?? internalAdapter ?? defaultWsAdapter ?? noopAdapter;
1046
+ const adapter = externalAdapter ?? baseUrlAdapter ?? workerUrlAdapter ?? internalAdapter ?? defaultWsAdapter ?? noopAdapter;
1047
+ const isUsingDefaultAdapter = adapter === defaultWsAdapter;
965
1048
  const shouldPoll = pollInterval > 0 && !externalScreenData;
966
1049
  const { data: polledScreenData, error: screenError } = useTerminalScreen(adapter, pollInterval, shouldPoll);
967
1050
  const { sendText: _sendText, sendKey: _sendKey } = useTerminalInput(adapter);
@@ -988,6 +1071,12 @@ function GreenScreenTerminal({
988
1071
  const sendKey = useCallback3(async (key) => _sendKey(key), [_sendKey]);
989
1072
  const [inputText, setInputText] = useState5("");
990
1073
  const [isFocused, setIsFocused] = useState5(false);
1074
+ const [showSignInHint, setShowSignInHint] = useState5(false);
1075
+ const prevAutoSignedIn = useRef4(false);
1076
+ useEffect4(() => {
1077
+ if (autoSignedIn && !prevAutoSignedIn.current) setShowSignInHint(true);
1078
+ prevAutoSignedIn.current = !!autoSignedIn;
1079
+ }, [autoSignedIn]);
991
1080
  const terminalRef = useRef4(null);
992
1081
  const inputRef = useRef4(null);
993
1082
  const [syncedCursor, setSyncedCursor] = useState5(null);
@@ -997,6 +1086,7 @@ function GreenScreenTerminal({
997
1086
  if (prevRawContentRef.current && newContent && newContent !== prevRawContentRef.current) {
998
1087
  setSyncedCursor(null);
999
1088
  setInputText("");
1089
+ if (showSignInHint) setShowSignInHint(false);
1000
1090
  }
1001
1091
  prevRawContentRef.current = newContent;
1002
1092
  }, [rawScreenData?.content]);
@@ -1044,17 +1134,34 @@ function GreenScreenTerminal({
1044
1134
  if (reconnectTimeoutRef.current) clearTimeout(reconnectTimeoutRef.current);
1045
1135
  };
1046
1136
  }, [connStatus?.connected, autoReconnectAttempt, isAutoReconnecting, reconnecting, reconnect, autoReconnectEnabled, maxAttempts, onNotification]);
1137
+ const [connecting, setConnecting] = useState5(false);
1138
+ const [signInError, setSignInError] = useState5(null);
1047
1139
  const handleSignIn = useCallback3(async (config) => {
1048
- onSignIn?.(config);
1049
- if (!externalAdapter && !baseUrlAdapter) {
1050
- const port = config.port ? `:${config.port}` : "";
1051
- const newAdapter = new RestAdapter({ baseUrl: `http://${config.host}${port}` });
1052
- setInternalAdapter(newAdapter);
1053
- await newAdapter.connect(config);
1140
+ if (onSignIn) {
1141
+ onSignIn(config);
1054
1142
  return;
1055
1143
  }
1056
- await connect(config);
1144
+ setConnecting(true);
1145
+ setSignInError(null);
1146
+ try {
1147
+ if (!externalAdapter && !baseUrlAdapter) {
1148
+ const port = config.port ? `:${config.port}` : "";
1149
+ const newAdapter = new RestAdapter({ baseUrl: `http://${config.host}${port}` });
1150
+ setInternalAdapter(newAdapter);
1151
+ await newAdapter.connect(config);
1152
+ if (config.username && config.password) setShowSignInHint(true);
1153
+ return;
1154
+ }
1155
+ await connect(config);
1156
+ if (config.username && config.password) setShowSignInHint(true);
1157
+ } catch (err) {
1158
+ setSignInError(err instanceof Error ? err.message : String(err));
1159
+ setConnecting(false);
1160
+ }
1057
1161
  }, [connect, onSignIn, externalAdapter, baseUrlAdapter]);
1162
+ useEffect4(() => {
1163
+ if (connecting && screenData?.content) setConnecting(false);
1164
+ }, [connecting, screenData?.content]);
1058
1165
  const [showBootLoader, setShowBootLoader] = useState5(bootLoader !== false);
1059
1166
  const [bootFadingOut, setBootFadingOut] = useState5(false);
1060
1167
  useEffect4(() => {
@@ -1065,6 +1172,35 @@ function GreenScreenTerminal({
1065
1172
  return () => clearTimeout(timer);
1066
1173
  }
1067
1174
  }, [screenData?.content, showBootLoader]);
1175
+ const FOCUS_STORAGE_KEY = "gs-terminal-focused";
1176
+ useEffect4(() => {
1177
+ if (!autoFocusDisabled && !readOnly) {
1178
+ try {
1179
+ if (localStorage.getItem(FOCUS_STORAGE_KEY) === "true") {
1180
+ setIsFocused(true);
1181
+ }
1182
+ } catch {
1183
+ }
1184
+ }
1185
+ }, []);
1186
+ useEffect4(() => {
1187
+ if (autoFocusDisabled) return;
1188
+ try {
1189
+ localStorage.setItem(FOCUS_STORAGE_KEY, String(isFocused));
1190
+ } catch {
1191
+ }
1192
+ }, [isFocused, autoFocusDisabled]);
1193
+ useEffect4(() => {
1194
+ if (isFocused) inputRef.current?.focus();
1195
+ }, [isFocused]);
1196
+ const hadScreenData = useRef4(false);
1197
+ useEffect4(() => {
1198
+ if (screenData?.content && !hadScreenData.current && !autoFocusDisabled && !readOnly) {
1199
+ hadScreenData.current = true;
1200
+ setIsFocused(true);
1201
+ }
1202
+ if (!screenData?.content) hadScreenData.current = false;
1203
+ }, [screenData?.content, autoFocusDisabled, readOnly]);
1068
1204
  useEffect4(() => {
1069
1205
  const handleClickOutside = (event) => {
1070
1206
  if (terminalRef.current && !terminalRef.current.contains(event.target)) setIsFocused(false);
@@ -1215,7 +1351,7 @@ function GreenScreenTerminal({
1215
1351
  const highlightedFields = fields.filter((f) => f.row === rowIndex && f.is_protected && f.is_highlighted);
1216
1352
  const reverseFields = fields.filter((f) => f.row === rowIndex && f.is_protected && f.is_reverse);
1217
1353
  const allRowFields = [...inputFields, ...highlightedFields, ...reverseFields];
1218
- if (allRowFields.length === 0) return /* @__PURE__ */ jsx4("span", { children: renderTextWithUnderlines(line, `r${rowIndex}`) });
1354
+ if (allRowFields.length === 0) return /* @__PURE__ */ jsx4("span", { children: line });
1219
1355
  const sorted = [...allRowFields].sort((a, b) => a.col - b.col);
1220
1356
  const segs = [];
1221
1357
  let lastEnd = 0;
@@ -1223,21 +1359,24 @@ function GreenScreenTerminal({
1223
1359
  sorted.forEach((field, idx) => {
1224
1360
  const fs = field.col;
1225
1361
  const fe = Math.min(field.col + field.length, cols);
1226
- if (fs > lastEnd) segs.push(/* @__PURE__ */ jsx4("span", { children: renderTextWithUnderlines(line.substring(lastEnd, fs), `r${rowIndex}p${idx}`) }, `t${idx}`));
1362
+ if (fs > lastEnd) segs.push(/* @__PURE__ */ jsx4("span", { children: line.substring(lastEnd, fs) }, `t${idx}`));
1227
1363
  const fc = line.substring(fs, fe);
1228
1364
  if (field.is_input) {
1229
- const w = field.length >= 30 ? Math.max(field.length, 40) : field.length;
1230
- segs.push(/* @__PURE__ */ jsx4("span", { className: "gs-input-field", style: { borderBottom: "2px solid var(--gs-green, #10b981)", display: "inline-block", minWidth: `${w}ch` }, children: fc }, `f${idx}`));
1365
+ const fieldWidth = Math.min(field.length, cols - fs);
1366
+ const fieldClass = showSignInHint ? "gs-confirmed-field" : field.is_underscored ? "gs-input-field" : void 0;
1367
+ segs.push(/* @__PURE__ */ jsx4("span", { className: fieldClass || void 0, style: { display: "inline-block", width: `${fieldWidth}ch`, overflow: "hidden" }, children: fc }, `f${idx}`));
1231
1368
  } else if (field.is_reverse) {
1232
1369
  segs.push(/* @__PURE__ */ jsx4("span", { style: { color: "#ef4444", fontWeight: "bold" }, children: fc }, `v${idx}`));
1233
- } else {
1370
+ } else if (field.is_highlighted) {
1234
1371
  segs.push(/* @__PURE__ */ jsx4("span", { style: { color: "var(--gs-white, #FFFFFF)" }, children: fc }, `h${idx}`));
1372
+ } else {
1373
+ segs.push(/* @__PURE__ */ jsx4("span", { children: fc }, `h${idx}`));
1235
1374
  }
1236
1375
  lastEnd = fe;
1237
1376
  });
1238
- if (lastEnd < line.length) segs.push(/* @__PURE__ */ jsx4("span", { children: renderTextWithUnderlines(line.substring(lastEnd), `r${rowIndex}e`) }, "te"));
1377
+ if (lastEnd < line.length) segs.push(/* @__PURE__ */ jsx4("span", { children: line.substring(lastEnd) }, "te"));
1239
1378
  return /* @__PURE__ */ jsx4(Fragment, { children: segs });
1240
- }, [renderTextWithUnderlines, screenData?.cols, profile.defaultCols]);
1379
+ }, [renderTextWithUnderlines, screenData?.cols, profile.defaultCols, showSignInHint]);
1241
1380
  const renderScreen = () => {
1242
1381
  if (showBootLoader && !screenData?.content) {
1243
1382
  if (bootLoader === false) return null;
@@ -1247,11 +1386,15 @@ function GreenScreenTerminal({
1247
1386
  if (bootFadingOut && screenData?.content) return /* @__PURE__ */ jsx4("div", { className: "gs-fade-in", children: renderScreenContent() });
1248
1387
  if (!screenData?.content) {
1249
1388
  if (inlineSignIn && !connStatus?.connected) {
1250
- return /* @__PURE__ */ jsx4("div", { style: { width: `${screenData?.cols || profile.defaultCols}ch`, height: `${(screenData?.rows || profile.defaultRows) * 21}px`, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsx4(InlineSignIn, { defaultProtocol: signInDefaultProtocol || protocol || "tn5250", loading: reconnecting, error: connectError, onConnect: handleSignIn }) });
1389
+ return /* @__PURE__ */ jsx4("div", { style: { width: "100%", height: `${(screenData?.rows || profile.defaultRows) * 21}px`, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsx4(InlineSignIn, { defaultProtocol: signInDefaultProtocol || protocol || "tn5250", loading: connecting || reconnecting, error: signInError || connectError, onConnect: handleSignIn }) });
1251
1390
  }
1252
1391
  return /* @__PURE__ */ jsx4("div", { style: { width: `${screenData?.cols || profile.defaultCols}ch`, height: `${(screenData?.rows || profile.defaultRows) * 21}px`, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsxs4("div", { style: { textAlign: "center" }, children: [
1253
1392
  /* @__PURE__ */ jsx4("div", { style: { color: "#808080", marginBottom: "12px" }, children: /* @__PURE__ */ jsx4(TerminalIcon, { size: 40 }) }),
1254
- /* @__PURE__ */ jsx4("p", { style: { fontFamily: "var(--gs-font)", fontSize: "12px", color: "#808080" }, children: connStatus?.connected ? "Waiting for screen data..." : "Not connected" })
1393
+ /* @__PURE__ */ jsx4("p", { style: { fontFamily: "var(--gs-font)", fontSize: "12px", color: "#808080" }, children: connStatus?.connected ? "Waiting for screen data..." : "Not connected" }),
1394
+ !connStatus?.connected && isUsingDefaultAdapter && /* @__PURE__ */ jsxs4("p", { style: { fontFamily: "var(--gs-font)", fontSize: "11px", color: "#606060", marginTop: "8px" }, children: [
1395
+ "Start the proxy: ",
1396
+ /* @__PURE__ */ jsx4("code", { style: { color: "#10b981" }, children: "npx green-screen-proxy --mock" })
1397
+ ] })
1255
1398
  ] }) });
1256
1399
  }
1257
1400
  return renderScreenContent();
@@ -1266,8 +1409,7 @@ function GreenScreenTerminal({
1266
1409
  const fields = screenData.fields || [];
1267
1410
  const ROW_HEIGHT = 21;
1268
1411
  const cursor = getCursorPos();
1269
- const hasInputFields = fields.some((f) => f.is_input);
1270
- const hasCursor = screenData.cursor_row !== void 0 && screenData.cursor_col !== void 0 && (hasInputFields || screenData.cursor_row !== 0 || screenData.cursor_col !== 0);
1412
+ const hasCursor = screenData.cursor_row !== void 0 && screenData.cursor_col !== void 0;
1271
1413
  return /* @__PURE__ */ jsxs4("div", { style: { fontFamily: "var(--gs-font)", fontSize: "13px", position: "relative", width: `${cols}ch` }, children: [
1272
1414
  rows.map((line, index) => {
1273
1415
  let displayLine = line;
@@ -1278,9 +1420,10 @@ function GreenScreenTerminal({
1278
1420
  const headerSegments = index === 0 ? profile.colors.parseHeaderRow(displayLine) : null;
1279
1421
  return /* @__PURE__ */ jsxs4("div", { className: headerSegments ? "" : profile.colors.getRowColorClass(index, displayLine, termRows), style: { height: `${ROW_HEIGHT}px`, lineHeight: `${ROW_HEIGHT}px`, whiteSpace: "pre", position: "relative" }, children: [
1280
1422
  headerSegments ? headerSegments.map((seg, i) => /* @__PURE__ */ jsx4("span", { className: seg.colorClass, children: seg.text }, i)) : renderRowWithFields(displayLine, index, fields),
1281
- hasCursor && index === cursor.row && /* @__PURE__ */ jsx4("span", { className: "gs-cursor", style: { position: "absolute", left: `${cursor.col}ch`, width: "1ch", height: `${ROW_HEIGHT}px`, top: 0, pointerEvents: "none" } })
1423
+ hasCursor && !showSignInHint && index === cursor.row && /* @__PURE__ */ jsx4("span", { className: "gs-cursor", style: { position: "absolute", left: `${cursor.col}ch`, width: "1ch", height: `${ROW_HEIGHT}px`, top: 0, pointerEvents: "none" } })
1282
1424
  ] }, index);
1283
1425
  }),
1426
+ showSignInHint && /* @__PURE__ */ jsx4("div", { className: "gs-signin-hint", children: "Signed in \u2014 press Enter to continue" }),
1284
1427
  screenData.cursor_row !== void 0 && screenData.cursor_col !== void 0 && /* @__PURE__ */ jsxs4("span", { style: { position: "absolute", bottom: 0, right: 0, fontFamily: "var(--gs-font)", fontSize: "10px", color: "var(--gs-green, #10b981)", pointerEvents: "none", opacity: 0.6 }, children: [
1285
1428
  String(screenData.cursor_row + 1).padStart(2, "0"),
1286
1429
  "/",