@cplieger/web-terminal-ui 6.1.5 → 7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cplieger/web-terminal-ui",
3
- "version": "6.1.5",
3
+ "version": "7.0.1",
4
4
  "type": "module",
5
5
  "description": "Reference touch-first browser UI for @cplieger/web-terminal-engine: a composable kernel + feature modules (tabs, activity monitor, mobile toolbar, context menu, IME, predictive echo, viewport handling) and CSS. One createTerminal() call.",
6
6
  "license": "MPL-2.0",
@@ -40,7 +40,7 @@
40
40
  "verify": "bash scripts/verify.sh"
41
41
  },
42
42
  "peerDependencies": {
43
- "@cplieger/web-terminal-engine": "^3.10.0 || ^4.0.0 || ^5.0.0"
43
+ "@cplieger/web-terminal-engine": "^5.0.3"
44
44
  },
45
45
  "overrides": {
46
46
  "qs": "^6.15.2"
@@ -188,9 +188,6 @@ export function contextMenu(opts: ContextMenuOptions = {}): TerminalFeature {
188
188
  clipboard.paste();
189
189
  });
190
190
  }
191
- if (menu.childElementCount === 0) {
192
- return; // nothing to offer
193
- }
194
191
  // Make visible (so it has measurable size), then place it within the
195
192
  // visible viewport via the shared point-anchored positioner (clamp to the
196
193
  // visual viewport; flip above the finger near the bottom edge).
@@ -244,7 +244,12 @@ async function createSessionHonouringRetry(
244
244
  isTornDown: () => boolean,
245
245
  ): Promise<SessionInfo> {
246
246
  const startedAt = Date.now();
247
- let lastAnnouncedAt = 0;
247
+ // The elapsed reading of the last announcement, or null when nothing has been
248
+ // announced yet. NOT a 0 sentinel: elapsed is legitimately 0 when the server
249
+ // refuses inside the first millisecond (a host on the same machine), and a 0
250
+ // sentinel read that as "never spoken" and re-announced on the next refusal —
251
+ // the one thing the throttle below exists to prevent.
252
+ let lastAnnouncedAt: number | null = null;
248
253
  for (;;) {
249
254
  try {
250
255
  return await api.create();
@@ -266,7 +271,7 @@ async function createSessionHonouringRetry(
266
271
  // only said "not yet" -- and the first announcement fires before any retry
267
272
  // has happened at all, so a user two seconds into a boot was told
268
273
  // "retrying" about a request that had been made exactly once.
269
- if (lastAnnouncedAt === 0 || elapsed - lastAnnouncedAt >= CREATE_RETRY_REANNOUNCE_MS) {
274
+ if (lastAnnouncedAt === null || elapsed - lastAnnouncedAt >= CREATE_RETRY_REANNOUNCE_MS) {
270
275
  lastAnnouncedAt = elapsed;
271
276
  const reason = err.serverMessage ?? "Server is not ready yet";
272
277
  ctx.toast(`${reason}; waiting…`, 8000);
@@ -601,7 +601,13 @@ function buildTerminal(
601
601
 
602
602
  connection.init({
603
603
  computeSize: render.computeSize,
604
- getHaveThrough: render.getHighestIndex,
604
+ // getHaveThrough is deliberately NOT supplied. The engine defaults it to its
605
+ // own renderer (engine >= 5.0.3), which is the only party that knows which
606
+ // store is currently bound and which of its rows the server has confirmed.
607
+ // Supplying it from here is what claimed rows the application had merely
608
+ // DRAWN — `render.getHighestIndex` is the window's bottom row — and left a
609
+ // frozen copy of the composer parked in scrollback on almost every reattach.
610
+ // An explicit member wins over the default, so re-adding one reopens that.
605
611
  // --- Demand-paged scrollback (engine docs/paged-scrollback.md §4-5) ---
606
612
  // The transport is store-blind and viewport-blind by design, so every one of
607
613
  // these forwards a decision only the renderer can make. Without them the
@@ -784,29 +790,29 @@ function buildTerminal(
784
790
  );
785
791
 
786
792
  /**
787
- * Applies the part of a `mapKeyboardEvent` result that BOTH keydown paths (the
788
- * focused textarea below, and the document-level type-to-focus fallback in the
789
- * focus-strategy section) handle identically. Returns false only for "ignore",
790
- * the one arm the two callers resolve differently: the textarea lets its own
791
- * `input` event deliver the character, while the fallback has no `input` event
792
- * coming and must send the character itself.
793
+ * Applies a `mapKeyboardEvent` result. BOTH keydown paths — the focused textarea
794
+ * below, and the document-level type-to-focus fallback in the focus-strategy
795
+ * section — handle every arm identically, including "ignore": the textarea lets
796
+ * its own `input` event deliver the character, and the fallback has already
797
+ * resolved that case before it gets here, computing the typed character itself
798
+ * because no `input` event is coming for a key that targeted the body.
793
799
  *
794
- * Switching over the whole `KeyboardResult` union in one place is what keeps
795
- * the two paths from drifting — handling only "send" in the fallback silently
796
- * dropped Shift+PageUp, which is exactly what a reader who just selected some
797
- * scrollback presses next.
800
+ * Switching over the whole `KeyboardResult` union in one place is what keeps the
801
+ * two paths from drifting — handling only "send" in the fallback silently dropped
802
+ * Shift+PageUp, which is exactly what a reader who just selected some scrollback
803
+ * presses next.
798
804
  */
799
- function applyMappedKey(ev: KeyboardEvent, result: keyboard.KeyboardResult): boolean {
805
+ function applyMappedKey(ev: KeyboardEvent, result: keyboard.KeyboardResult): void {
800
806
  switch (result.kind) {
801
807
  case "send":
802
808
  ev.preventDefault();
803
809
  sendText(result.bytes);
804
- return true;
810
+ return;
805
811
  case "scroll-up": {
806
812
  ev.preventDefault();
807
813
  const h = termWrap.clientHeight;
808
814
  termWrap.scrollTop = Math.max(0, termWrap.scrollTop - h);
809
- return true;
815
+ return;
810
816
  }
811
817
  case "scroll-down": {
812
818
  ev.preventDefault();
@@ -817,10 +823,10 @@ function buildTerminal(
817
823
  // reasoning as the engine's bottom writes.
818
824
  const max = Math.max(0, termWrap.scrollHeight - h);
819
825
  termWrap.scrollTop = Math.min(max, termWrap.scrollTop + h);
820
- return true;
826
+ return;
821
827
  }
822
828
  case "ignore":
823
- return false;
829
+ return;
824
830
  }
825
831
  }
826
832
 
@@ -836,9 +842,9 @@ function buildTerminal(
836
842
  return;
837
843
  }
838
844
  }
839
- // A false return ("ignore") needs nothing here: the `input` listener above
840
- // delivers the character, which is also how IME and dead-key composition
841
- // output reaches the terminal.
845
+ // Nothing more is needed for an "ignore": the `input` listener above delivers
846
+ // the character, which is also how IME and dead-key composition output
847
+ // reaches the terminal.
842
848
  applyMappedKey(ev, mapKeyboardEvent(ev, modes));
843
849
  },
844
850
  { signal },