@cplieger/web-terminal-ui 3.4.3 → 3.4.4

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 CHANGED
@@ -102,7 +102,7 @@ individual features instead:
102
102
  - `presetAgentTabbed()` — the same feature set as `presetTabbed()` (activity
103
103
  monitor included), but with `preferInputTitle`: each tab's label follows the
104
104
  latest submitted line (persisted server-side and recovered on reload) and the
105
- program's OSC 0/2 title is ignored — for an agent shell such as `vibecli`
105
+ program's OSC 0/2 title is ignored — for an agent shell such as `web-terminal-kiro`
106
106
  whose program emits a non-empty but useless OSC title. Its status dots
107
107
  (idle / working / done / needs-input) come from the same activity monitor,
108
108
  driven by the server's status SSE and its OSC-9 classifier.
@@ -143,7 +143,7 @@ The web-terminal family:
143
143
  Consumers that ship this UI:
144
144
 
145
145
  - [`vibekit`](https://github.com/cplieger/vibekit)
146
- - [`vibecli`](https://github.com/cplieger/vibecli)
146
+ - [`web-terminal-kiro`](https://github.com/cplieger/web-terminal-kiro)
147
147
 
148
148
  ## Disclaimer
149
149
 
package/css/00-tokens.css CHANGED
@@ -10,7 +10,7 @@
10
10
  /* ===== Themeable tokens (the consumer-configurable "template") =====
11
11
  These default to the neutral built-in look; a consumer recolors the UI by
12
12
  overriding them via createTerminal's `theme` option (which sets them on the
13
- terminal root) or its own CSS. vibecli sets a purple palette. Everything
13
+ terminal root) or its own CSS. web-terminal-kiro sets a purple palette. Everything
14
14
  above is the fixed design palette; everything here is the public theme API. */
15
15
  --accent: oklch(72% 0.15 250deg); /* light blue; themed per consumer */
16
16
  --tab-bg: oklch(30% 0 0deg / 90%); /* inactive-tab chip (translucent) */
package/css/01-reset.css CHANGED
@@ -14,6 +14,12 @@ body {
14
14
  background: var(--bg);
15
15
  color-scheme: dark;
16
16
  -webkit-font-smoothing: antialiased;
17
+ /* Disable the browser's landscape text autosizing ("text inflation"): without
18
+ this, rotating a phone to landscape zooms the text. iOS Safari implements
19
+ ONLY the -webkit- prefixed form (the unprefixed property is ignored there),
20
+ so both are required — unprefixed alone left iPhones inflating on rotate. */
21
+ /* stylelint-disable-next-line property-no-vendor-prefix -- iOS Safari only implements the prefixed form */
22
+ -webkit-text-size-adjust: 100%;
17
23
  text-size-adjust: 100%;
18
24
  overscroll-behavior: none;
19
25
  /* Disable pinch-to-zoom and double-tap-to-zoom. The viewport meta tag
package/css/05-tabs.css CHANGED
@@ -72,7 +72,7 @@
72
72
 
73
73
  /* The active tab reads clearly above the inactive chips: a stronger fill plus an
74
74
  accent-coloured edge (inactive chips carry a faint white --border; the active
75
- one gets the accent equivalent). Themeable via --accent (vibecli -> purple). */
75
+ one gets the accent equivalent). Themeable via --accent (web-terminal-kiro -> purple). */
76
76
  .wt-tab-active {
77
77
  background: var(--tab-active-bg);
78
78
  border-color: var(--tab-active-border);
@@ -195,24 +195,21 @@
195
195
  }
196
196
 
197
197
  /* Desktop-strip keyboard button (tabs feature): opens the on-screen key grid on
198
- a WIDE touchscreen (an iPad using the multi-tab strip), where there is no
199
- mobile switcher to carry one. Built + wired by the SAME factory as the mobile
200
- switcher's keyboard button (see makeKbButton in tabs.ts). Uses the shared
201
- .wt-btn primitive for its 44px box + centered icon. Hidden on a fine-pointer
202
- desktop (a real keyboard is present) and until a keyboardToggle feature is
203
- wired ([hidden]); shown only on a coarse pointer at strip widths (the media
204
- block below, kept in lockstep with SWITCHER_MEDIA in src/features/tabs.ts).
205
- Fixed at the strip's bottom-right — above the fixed bottom strip, with a
206
- matching-surface fill — so it caps the strip's right edge and never scrolls
207
- with the tabs. */
198
+ a WIDE touchscreen (an iPad using the multi-tab strip, or a phone rotated to
199
+ landscape), where there is no mobile switcher to carry one. Built + wired by
200
+ the SAME factory as the mobile switcher's keyboard button (see makeKbButton
201
+ in tabs.ts). Uses the shared .wt-btn primitive for its 44px box + centered
202
+ icon, borderless like the switcher's — the old fixed-overlay styling gave it
203
+ a border and pinned it right of the "+", which read as a different control
204
+ the moment a phone rotated to landscape. Now an ordinary flex item inside the
205
+ strip, before the "+" (kb-then-new, mirroring the mobile switcher's order).
206
+ Hidden on a fine-pointer desktop (a real keyboard is present) and until a
207
+ keyboardToggle feature is wired ([hidden]); shown only on a coarse pointer at
208
+ strip widths (the media block below, kept in lockstep with SWITCHER_MEDIA in
209
+ src/features/tabs.ts). */
208
210
  .wt-tab-kb {
209
211
  display: none;
210
- position: fixed;
211
- right: var(--sp-2);
212
- bottom: calc(var(--safe-bottom) + var(--sp-2));
213
- z-index: 16;
214
- background: var(--surface);
215
- border: 1px solid var(--border);
212
+ flex: none;
216
213
  }
217
214
 
218
215
  /* Grid open: filled so the toggled state reads at a glance (mirrors the
@@ -229,17 +226,13 @@
229
226
  color: var(--bg);
230
227
  }
231
228
 
232
- /* Show + reserve room only on a WIDE touchscreen (where the desktop strip is
233
- used AND a keyboardToggle is wired, hence :not([hidden])): the strip gets
234
- matching right padding so the tabs + "+" never scroll under the fixed button. */
229
+ /* Show only on a WIDE touchscreen (where the desktop strip is used AND a
230
+ keyboardToggle is wired, hence :not([hidden])). In-flow now, so the strip
231
+ needs no reserved right padding for it. */
235
232
  @media (pointer: coarse) and (width >= 601px) {
236
233
  .wt-tab-kb:not([hidden]) {
237
234
  display: inline-flex;
238
235
  }
239
-
240
- .wt-tab-bar {
241
- padding-right: calc(var(--touch-target) + 2 * var(--sp-2));
242
- }
243
236
  }
244
237
 
245
238
  /* The tab being dragged (reorder) dims while it moves. */
package/css/06-mobile.css CHANGED
@@ -111,7 +111,7 @@
111
111
  /* Right padding reserves room for the close (x) overlaid inside the row. */
112
112
  padding: 0 var(--touch-target) 0 var(--sp-2);
113
113
  /* The bar row always represents the active tab, so it wears the active-tab
114
- fill/foreground the desktop strip uses (themeable; purple in vibecli). */
114
+ fill/foreground the desktop strip uses (themeable; purple in web-terminal-kiro). */
115
115
  background: var(--tab-active-bg);
116
116
  color: var(--tab-active-fg);
117
117
  border: 1px solid var(--tab-active-border);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cplieger/web-terminal-ui",
3
- "version": "3.4.3",
3
+ "version": "3.4.4",
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": "GPL-3.0-or-later",
@@ -13,8 +13,8 @@
13
13
  ],
14
14
  "types": "./src/index.ts",
15
15
  "scripts": {
16
- "typecheck": "tsgo -p tsconfig.json",
17
- "typecheck:tests": "tsgo -p tsconfig.test.json",
16
+ "typecheck": "tsc -p tsconfig.json",
17
+ "typecheck:tests": "tsc -p tsconfig.test.json",
18
18
  "test": "vitest --run",
19
19
  "test:watch": "vitest",
20
20
  "test:coverage": "vitest --run --coverage",
@@ -29,19 +29,20 @@
29
29
  },
30
30
  "devDependencies": {
31
31
  "@eslint/js": "10.0.1",
32
- "@types/node": "25.9.4",
33
- "@vitest/coverage-v8": "4.1.9",
34
- "eslint": "10.6.0",
35
- "fast-check": "4.8.0",
32
+ "@types/node": "25.9.5",
33
+ "@typescript/native": "npm:typescript@7.0.2",
34
+ "@vitest/coverage-v8": "4.1.10",
35
+ "eslint": "10.7.0",
36
+ "fast-check": "4.9.0",
36
37
  "happy-dom": "20.10.6",
37
38
  "html-validate": "11.5.5",
38
- "knip": "6.24.0",
39
- "prettier": "3.9.4",
39
+ "knip": "6.26.0",
40
+ "prettier": "3.9.5",
40
41
  "stylelint": "17.14.0",
41
42
  "stylelint-config-standard": "40.0.0",
42
- "typescript": "6.0.3",
43
- "typescript-eslint": "8.62.1",
44
- "vitest": "4.1.9"
43
+ "typescript": "npm:@typescript/typescript6@6.0.2",
44
+ "typescript-eslint": "8.63.0",
45
+ "vitest": "4.1.10"
45
46
  },
46
47
  "publishConfig": {
47
48
  "access": "public"
@@ -9,10 +9,12 @@
9
9
  <link rel="stylesheet" href="/style.css">
10
10
  <!-- [html-validate-disable-next no-style-tag -- critical CSS for instant loading indicator] -->
11
11
  <style>
12
- #loading{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:12px;z-index:200;background:#000;font-family:system-ui,-apple-system,sans-serif;color:#b48eff;font-size:18px;transition:opacity .3s ease}
12
+ #loading{position:fixed;inset:0;z-index:200;display:flex;align-items:center;justify-content:center;background:#000;color:var(--accent,#6ea8ff);transition:opacity .3s ease}
13
13
  #loading.fade{opacity:0}
14
- #loading .spin{width:32px;height:32px;border:3px solid #b48eff;border-right-color:transparent;border-radius:50%;animation:sp .6s linear infinite}
15
- @keyframes sp{to{transform:rotate(360deg)}}
14
+ #loading .bar{position:relative;inline-size:min(220px,62vw);block-size:5px;overflow:hidden;border-radius:999px;background:color-mix(in oklch,currentColor 16%,transparent)}
15
+ #loading .bar::before{content:"";position:absolute;inset-block:0;inline-size:42%;border-radius:inherit;background:linear-gradient(90deg,transparent,currentColor 28%,color-mix(in oklch,currentColor,#fff 45%) 50%,currentColor 72%,transparent);filter:drop-shadow(0 0 4px color-mix(in oklch,currentColor 55%,transparent));animation:wt-loading-slide 1.5s cubic-bezier(.65,0,.35,1) infinite}
16
+ @keyframes wt-loading-slide{0%{transform:translateX(-110%)}100%{transform:translateX(250%)}}
17
+ @media (prefers-reduced-motion:reduce){#loading .bar::before{animation-duration:3s}}
16
18
  </style>
17
19
  </head>
18
20
  <body>
@@ -20,7 +22,7 @@
20
22
  element. The loading overlay is a sibling (not a child of the root) so it is
21
23
  never cleared; it is faded out once the first frame renders. -->
22
24
  <div id="terminal"></div>
23
- <div id="loading"><div class="spin"></div>Loading…</div>
25
+ <div id="loading" role="status" aria-label="Loading"><div class="bar" aria-hidden="true"></div></div>
24
26
  <!-- Deployment-specific: the importmap resolves the bare package specifiers to
25
27
  the compiled JS your build emits. Adjust the paths to your build layout. -->
26
28
  <script type="importmap">
@@ -146,7 +146,7 @@ export interface TabsOptions {
146
146
  /** Prefer the input-derived title over the process OSC 0/2 title. Default
147
147
  * false (OSC-first: a program that sets its own window title wins, else the
148
148
  * last submitted line). Set true for an agent shell whose program emits a
149
- * non-empty but useless OSC title (kiro-cli under vibecli): the label then
149
+ * non-empty but useless OSC title (kiro-cli under web-terminal-kiro): the label then
150
150
  * follows the latest submitted line (live) or the persisted client title (on
151
151
  * reload), and the unreliable OSC title is ignored entirely. presetAgentTabbed
152
152
  * enables this. */
@@ -359,7 +359,7 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
359
359
  }
360
360
  // Persist the input-derived tab title server-side so it survives a page reload
361
361
  // and shows on other devices. The engine stores it as the session's fallback
362
- // title and uses it only when the program emits no OSC title (the vibecli case:
362
+ // title and uses it only when the program emits no OSC title (the web-terminal-kiro case:
363
363
  // kiro-cli emits no usable OSC title, so the latest user message is the label).
364
364
  // Best-effort and fire-and-forget: a failure never disrupts the terminal — the
365
365
  // locally-derived title still displays, it just is not persisted this time.
@@ -458,20 +458,19 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
458
458
  bar.className = "wt-tab-bar";
459
459
  bar.setAttribute("role", "tablist");
460
460
  slot.appendChild(bar);
461
- // The "+" button is the last flex item INSIDE the strip. Appending it to
462
- // the slot (a sibling of the fixed .wt-tab-bar) flowed it out of the strip
463
- // to the root, so it never appeared; inside the flex bar it renders at the
464
- // end of the tabs (addTabChrome inserts each tab before it).
461
+ // Strip-end controls, both flex items INSIDE the strip so they flow after
462
+ // the tabs (appending them to the slot flowed them out of the strip):
463
+ // the keyboard button first, then the "+" — mirroring the mobile
464
+ // switcher's kb-before-new order, so rotating a phone to landscape (which
465
+ // swaps in this strip) keeps the keyboard button LEFT of the "+" rather
466
+ // than jumping to its right. Built + wired by the same shared factories
467
+ // as the mobile switcher's; the kb button is CSS-gated to a wide
468
+ // touchscreen and un-hidden below only when a keyboardToggle is wired.
469
+ // addTabChrome inserts each tab before deskKb, keeping [tabs… kb +].
470
+ const deskKb = makeKbButton("wt-tab-kb wt-btn");
471
+ bar.appendChild(deskKb);
465
472
  const newBtn = makeNewButton("wt-tab-new");
466
473
  bar.appendChild(newBtn);
467
- // Desktop-strip keyboard button, built + wired by the SAME factory as the
468
- // mobile switcher's. A SIBLING of the strip (appended to the region slot,
469
- // not into .wt-tab-bar) so it never affects the strip's child order; CSS
470
- // shows it only on a wide touchscreen (where the strip, not the switcher,
471
- // is used) and hides it on a fine-pointer desktop. Un-hidden below only
472
- // when a keyboardToggle feature is wired.
473
- const deskKb = makeKbButton("wt-tab-kb wt-btn");
474
- slot.appendChild(deskKb);
475
474
 
476
475
  // Pull the terminal surface up off the fixed BOTTOM strip on desktop so
477
476
  // the bar does not overlap the last rows. The surface is position:fixed
@@ -522,8 +521,12 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
522
521
  swBar.append(swKb, swSwitch, swNew);
523
522
  // Latest-wins notification state for the switch button's dot: overwritten
524
523
  // by each qualifying background-tab event (applyStatus) and cleared when
525
- // the list opens (expandSwitcher). "" = no pending cue; the dot is hidden.
524
+ // the list opens (expandSwitcher) or the raising tab is visited or closed
525
+ // (acknowledgeSwitchNotify). "" = no pending cue; the dot is hidden.
526
526
  let switchNotify: "" | "input" | "done" = "";
527
+ // The session that raised the pending cue, so arriving on that tab (a
528
+ // swipe or any switch) acknowledges it without opening the list.
529
+ let switchNotifyId: string | null = null;
527
530
  function paintSwitchDot(): void {
528
531
  // Reuse the per-tab status-dot colours (single source, css/05-tabs.css
529
532
  // .wt-status-dot[data-status="input"|"done"]) instead of re-declaring
@@ -535,6 +538,21 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
535
538
  swSwitchDot.dataset["status"] = switchNotify;
536
539
  }
537
540
  }
541
+ function clearSwitchNotify(): void {
542
+ switchNotify = "";
543
+ switchNotifyId = null;
544
+ paintSwitchDot();
545
+ }
546
+ // acknowledgeSwitchNotify clears the pending cue when its subject session
547
+ // is reached (switchTo — including a swipe arriving on it) or ceases to
548
+ // exist (a close/reap): the notification is resolved or moot then, and
549
+ // only opening the list used to clear it (the reported "swiping to the
550
+ // concerned tab leaves the dot lit").
551
+ function acknowledgeSwitchNotify(id: string): void {
552
+ if (switchNotifyId === id) {
553
+ clearSwitchNotify();
554
+ }
555
+ }
538
556
  ctx.region("bottom-switcher", "switcher").appendChild(switcher);
539
557
  // The keyboard buttons open the key grid; show them only when a toolbar is
540
558
  // wired to drive. Read the toolbar's API lazily at tap time (ctx.use), so
@@ -1223,8 +1241,7 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
1223
1241
  }
1224
1242
  // Opening the list acknowledges any pending background-tab notification:
1225
1243
  // the user is now looking at the tabs, so clear the switch button's dot.
1226
- switchNotify = "";
1227
- paintSwitchDot();
1244
+ clearSwitchNotify();
1228
1245
  renderSwitcherList();
1229
1246
  setExpandedState(true);
1230
1247
  ctx.announce("Terminal list expanded");
@@ -1257,8 +1274,9 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
1257
1274
  }
1258
1275
  paintStatusDot(dot, info.status, info.reportsActivity ?? false);
1259
1276
  const aria = tablist.registerTab(el);
1260
- // Insert before the "+" button so it stays the last item in the strip.
1261
- bar.insertBefore(el, newBtn);
1277
+ // Insert before the strip-end controls (keyboard button, then "+") so
1278
+ // they stay the last items in the strip.
1279
+ bar.insertBefore(el, deskKb);
1262
1280
  // Runtime-added tabs animate in; initial tabs do not (see `started`).
1263
1281
  // The timer (not animationend) also clears the class on the hidden mobile
1264
1282
  // strip, where the animation never fires.
@@ -1390,6 +1408,10 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
1390
1408
  // rebuild viewport-first, so the last-known screen paints with no
1391
1409
  // round-trip. Then let the kernel reconnect the WS to it (resume delta).
1392
1410
  activeId = next.id;
1411
+ // Arriving on the tab that raised the switch-button cue resolves it
1412
+ // (a swipe through the tabs must dismiss the dot, not only opening the
1413
+ // list).
1414
+ acknowledgeSwitchNotify(next.id);
1393
1415
  try {
1394
1416
  localStorage.setItem(ACTIVE_TAB_KEY, next.id);
1395
1417
  } catch {
@@ -1522,9 +1544,9 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
1522
1544
 
1523
1545
  // dragTargetBefore returns the first tab whose horizontal midpoint is past
1524
1546
  // x (the element the dragged tab should sit before), or null to drop at the
1525
- // end (before the "+"). syncOrderFromDom rebuilds tabList to match the
1526
- // strip's DOM order after a drag, so position indicators, the switcher, and
1527
- // close-to-the-right/left all follow the visible order.
1547
+ // end (before the strip-end controls). syncOrderFromDom rebuilds tabList to
1548
+ // match the strip's DOM order after a drag, so position indicators, the
1549
+ // switcher, and close-to-the-right/left all follow the visible order.
1528
1550
  function dragTargetBefore(x: number): HTMLElement | null {
1529
1551
  for (const el of bar.querySelectorAll<HTMLElement>(".wt-tab:not(.wt-tab-dragging)")) {
1530
1552
  const rect = el.getBoundingClientRect();
@@ -1647,6 +1669,9 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
1647
1669
  // Tombstone this id briefly so a stale status snapshot/poll that predates
1648
1670
  // the server reaping it cannot re-adopt the just-closed tab.
1649
1671
  tombstone(id);
1672
+ // A pending switch-button cue whose subject just closed is moot: clear
1673
+ // it rather than leaving a dot no tab visit can ever resolve.
1674
+ acknowledgeSwitchNotify(id);
1650
1675
  tab.aria.remove();
1651
1676
  // Remove immediately (no exit animation): a lingering element made the
1652
1677
  // "+" teleport after a delay, and made a last-tab replacement appear in
@@ -1711,6 +1736,7 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
1711
1736
  // server reaping these sessions cannot re-adopt (flash back) a just-
1712
1737
  // closed tab -- mirrors the single-close path in dropTab (h-f2).
1713
1738
  tombstone(t.id);
1739
+ acknowledgeSwitchNotify(t.id); // a cue for a closed tab is moot
1714
1740
  t.aria.remove();
1715
1741
  t.el.remove();
1716
1742
  ctx.dropSession(t.id);
@@ -1880,6 +1906,7 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
1880
1906
  // dedicated button.
1881
1907
  if (id !== activeId && (status === "input" || status === "done")) {
1882
1908
  switchNotify = status;
1909
+ switchNotifyId = id;
1883
1910
  paintSwitchDot();
1884
1911
  }
1885
1912
  // Record the raw server title; the displayed label (fallback + de-dup)
@@ -1914,15 +1941,20 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
1914
1941
  return;
1915
1942
  }
1916
1943
  // Adopt a session created in another browser so all clients converge.
1944
+ // Carry the persisted clientTitle through: the SSE snapshot usually
1945
+ // beats the initial GET /api/sessions (whose adoptSession then dedups
1946
+ // and never re-applies its fields), so dropping it here left every
1947
+ // SSE-adopted tab without its persisted title — in preferInputTitle
1948
+ // mode the label then fell back to "New tab" on reload.
1917
1949
  adoptSession({
1918
1950
  id: s.id,
1919
1951
  title: s.title,
1920
- clientTitle: undefined,
1952
+ clientTitle: s.clientTitle,
1921
1953
  createdAt: s.createdAt,
1922
1954
  status: s.status,
1923
1955
  reportsActivity: s.reportsActivity ?? false,
1924
1956
  });
1925
- applyStatus(s.id, s.status, s.title, undefined, s.reportsActivity ?? false);
1957
+ applyStatus(s.id, s.status, s.title, s.clientTitle, s.reportsActivity ?? false);
1926
1958
  ensureActive();
1927
1959
  syncChrome();
1928
1960
  });
@@ -1994,7 +2026,7 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
1994
2026
  if (e.dataTransfer) {
1995
2027
  e.dataTransfer.dropEffect = "move";
1996
2028
  }
1997
- bar.insertBefore(draggingEl, dragTargetBefore(e.clientX) ?? newBtn);
2029
+ bar.insertBefore(draggingEl, dragTargetBefore(e.clientX) ?? deskKb);
1998
2030
  });
1999
2031
  // Active-row close (x): closes the current tab (mirrors a listed row's x).
2000
2032
  // stopPropagation so it is not read as a tap/swipe on the row surface.
@@ -2054,9 +2086,16 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
2054
2086
  return;
2055
2087
  }
2056
2088
  const target = e.target as HTMLElement | null;
2089
+ // The tab strip counts as chrome like the switcher: without it, the
2090
+ // desktop-strip keyboard button's own pointerup landed here, closed the
2091
+ // grid, and the button's click then re-opened it — so tapping the button
2092
+ // to CLOSE the grid never worked (a wide-touchscreen / landscape-phone
2093
+ // bug; the switcher's kb button was already exempt via .wt-switcher).
2057
2094
  const inChrome =
2058
2095
  target !== null &&
2059
- (target.closest(".wt-switcher") !== null || target.closest(".key-toolbar") !== null);
2096
+ (target.closest(".wt-switcher") !== null ||
2097
+ target.closest(".wt-tab-bar") !== null ||
2098
+ target.closest(".key-toolbar") !== null);
2060
2099
  if (inChrome) {
2061
2100
  return;
2062
2101
  }
package/src/presets.ts CHANGED
@@ -66,7 +66,7 @@ function buildTabbed(preferInputTitle: boolean): TerminalFeature<unknown>[] {
66
66
  /** Tabbed UI: the touch features, tabs, the mobile keyboard bar, the activity
67
67
  * monitor, and animations. Requires a server that speaks the session API
68
68
  * (`/api/sessions`, `/ws?session=`, and the status SSE `/api/sessions/events`),
69
- * such as `web-terminal-server` or `vibecli`. Each tab's title follows the
69
+ * such as `web-terminal-server` or `web-terminal-kiro`. Each tab's title follows the
70
70
  * process OSC 0/2 window title when the program sets one, else the last
71
71
  * submitted line; each tab's activity dot stays hidden until its session
72
72
  * reports activity via OSC 9;4 progress (kiro-cli, Claude Code, …), so a plain
@@ -75,7 +75,7 @@ export function presetTabbed(): TerminalFeature<unknown>[] {
75
75
  return buildTabbed(false);
76
76
  }
77
77
 
78
- /** Tabbed UI for an agent shell (vibecli). Same features as presetTabbed, but
78
+ /** Tabbed UI for an agent shell (web-terminal-kiro). Same features as presetTabbed, but
79
79
  * with `preferInputTitle`: the agent's program (kiro-cli) emits a non-empty but
80
80
  * useless OSC 0/2 title, so each tab's label follows the latest submitted line
81
81
  * (persisted server-side and recovered on reload) and the OSC title is ignored.