@co0ontty/wand 1.25.1 → 1.25.3

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.
@@ -1076,6 +1076,49 @@
1076
1076
  scheduleForegroundSync("android-resume", { immediate: true });
1077
1077
  ensureTerminalFitWithRetry("android-resume");
1078
1078
  });
1079
+
1080
+ // Bridge from Android IME animation. State values: "start" / "shown" / "hidden".
1081
+ // 原生层用 setPadding 在 WebView 父容器上 resize WebView, 视觉上键盘
1082
+ // 动画跟系统同步, 但带来一个副作用: window.innerHeight === visualViewport.height,
1083
+ // 导致 setupVisualViewportHandlers 里的 isKeyboardOpen 检测 (基于
1084
+ // offsetBottom) 永远是 false, 不会进 keyboard-open / keyboard-close 分支,
1085
+ // 终端 forceReplay 路径也就不跑了。
1086
+ //
1087
+ // 这里直接听原生层的"键盘动画收尾"事件, 触发 ensureTerminalFit
1088
+ // (forceReplay=true), 把 wterm 的网格按真实视口重排一遍。
1089
+ window.addEventListener("wand-ime-state", function(e) {
1090
+ var which = e && e.detail && e.detail.state;
1091
+ if (which === "shown" || which === "hidden") {
1092
+ try {
1093
+ ensureTerminalFit("native-ime-" + which, { forceReplay: true });
1094
+ maybeScrollTerminalToBottom("native-ime");
1095
+ } catch (_e) {}
1096
+ }
1097
+ });
1098
+
1099
+ // Bridge from Android ConnectivityManager.NetworkCallback. State values:
1100
+ // "available" — 默认网络刚刚可用 (启动期没网 → 接上)
1101
+ // "changed" — 已有网络切到另一个 (Wi-Fi ↔ 4G), socket 必死
1102
+ // "validated" — captive portal / VPN 验证完成, internet 才真正通
1103
+ // "lost" — 默认网络断了, 还没有备援网络
1104
+ // 前三种都强制重连; "lost" 不动 socket, 只更新 isOnline 让 UI 提示。
1105
+ // 这条路径比 navigator.online / visibilitychange 早 2-8 秒触发,
1106
+ // 切网后用户基本看不到断线提示。
1107
+ window.addEventListener("wand-android-network", function(e) {
1108
+ var which = e && e.detail && e.detail.state;
1109
+ if (which === "lost") {
1110
+ state.isOnline = false;
1111
+ try { updateOfflineBanner(); } catch (_e) {}
1112
+ return;
1113
+ }
1114
+ if (which === "available" || which === "changed" || which === "validated") {
1115
+ // 以原生信号为权威, 立刻翻 isOnline 给 UI; 有些 ROM 上
1116
+ // navigator.onLine 要等几秒才更新, 否则 banner 会闪一下。
1117
+ state.isOnline = true;
1118
+ try { updateOfflineBanner(); } catch (_e) {}
1119
+ forceReconnectWebSocket("android-network-" + which);
1120
+ }
1121
+ });
1079
1122
  }
1080
1123
 
1081
1124
  function restoreLoginSession() {
@@ -12868,20 +12911,31 @@
12868
12911
  // 这里在 visualViewport 检测到键盘收起的瞬间直接强制复位一次,
12869
12912
  // 并把 --app-viewport-height 兜底清掉,确保 .app-container 回到
12870
12913
  // 100dvh、input-panel 重新贴屏幕底部。
12914
+ //
12915
+ // Android APK (window.__wandImeNative=true) 跳过这段 iOS hack —
12916
+ // MainActivity 已经在 IME 动画 callback 里逐帧把 root setPadding,
12917
+ // WebView 直接被原生层 resize 推回到了正确位置, 这里再调
12918
+ // scrollTo(0,0) 反而会跟原生 padding 打架, 偶尔产生一帧抖。
12919
+ // 只保留 refit / 滚底两个纯展示动作。
12871
12920
  var rootEl = document.documentElement;
12872
- rootEl.style.removeProperty('--app-viewport-height');
12873
- window.scrollTo(0, 0);
12874
- if (document.scrollingElement) document.scrollingElement.scrollTop = 0;
12875
- rootEl.scrollTop = 0;
12876
- if (document.body) document.body.scrollTop = 0;
12877
- setTimeout(function() {
12878
- // 二次复位:等键盘收起动画 + iOS 自身的回滚跑完后再清一次,
12879
- // 防止 iOS 在动画过程中又把 scrollTop 推上去。
12921
+ var imeIsNative = !!window.__wandImeNative;
12922
+ if (!imeIsNative) {
12923
+ rootEl.style.removeProperty('--app-viewport-height');
12880
12924
  window.scrollTo(0, 0);
12881
12925
  if (document.scrollingElement) document.scrollingElement.scrollTop = 0;
12882
12926
  rootEl.scrollTop = 0;
12883
12927
  if (document.body) document.body.scrollTop = 0;
12884
- syncAppViewportHeight();
12928
+ }
12929
+ setTimeout(function() {
12930
+ if (!imeIsNative) {
12931
+ // 二次复位:等键盘收起动画 + iOS 自身的回滚跑完后再清一次,
12932
+ // 防止 iOS 在动画过程中又把 scrollTop 推上去。
12933
+ window.scrollTo(0, 0);
12934
+ if (document.scrollingElement) document.scrollingElement.scrollTop = 0;
12935
+ rootEl.scrollTop = 0;
12936
+ if (document.body) document.body.scrollTop = 0;
12937
+ syncAppViewportHeight();
12938
+ }
12885
12939
  ensureTerminalFit("keyboard-close", { forceReplay: true });
12886
12940
  maybeScrollTerminalToBottom("force");
12887
12941
  }, 200);
@@ -151,14 +151,26 @@
151
151
  Android 自 targetSdk=35 起强制边到边, WebView 内容会绘制到状态栏底下,
152
152
  但 Android WebView 不会主动把 WindowInsets 暴露给 env(safe-area-inset-*),
153
153
  所以纯 env() 在绝大多数 Android 机器上回落到 0 -> 顶部直接撞到状态栏。
154
- 这里给 Wand APK 兜一个 32px 最小值, 确保即使旧版 APK 还没装 inset 注入
155
- 桥的时候, 顶部抽屉/模态也不会撞到状态栏。新版 APK 注入实际 inset 后,
156
- max() 会自动取更精准的值。*/
154
+ 这里给旧版 Wand APK 兜一个 32px 最小值, 确保它们的顶部抽屉/模态不撞
155
+ 状态栏。新版 APK 已经在原生层 setPadding 把 WebView 整体往里缩 (见
156
+ MainActivity.installWindowInsetsBridge) 并注入 .is-wand-app-native-insets
157
+ 类来关掉这个兜底, 否则会双重 inset 浪费空间。*/
157
158
  .is-wand-app {
158
159
  --wand-safe-top: max(var(--app-inset-top), env(safe-area-inset-top, 0px), 32px);
159
160
  --wand-safe-bottom: max(var(--app-inset-bottom), env(safe-area-inset-bottom, 0px), 0px);
160
161
  }
161
162
 
163
+ /* 新版 APK 原生已经 padding 过, 不需要 CSS 再加任何 safe-area。把所有
164
+ --wand-safe-* / --pwa-safe-top 全部归零, 让顶部抽屉 / 模态 / 顶栏直接顶
165
+ 到 WebView 内容上沿 (= 状态栏下沿)。*/
166
+ .is-wand-app-native-insets {
167
+ --wand-safe-top: 0px;
168
+ --wand-safe-bottom: 0px;
169
+ --wand-safe-left: 0px;
170
+ --wand-safe-right: 0px;
171
+ --pwa-safe-top: 0px;
172
+ }
173
+
162
174
  /* 在普通移动浏览器 / Android WebView 边到边渲染 / 旋转后地址栏隐藏等场景下,
163
175
  .app-container 同样需要消费 safe-area-inset-top, 否则 .main-header-row
164
176
  会被状态栏挡住。inset 为 0 的设备上为 0px, 不影响桌面。*/
@@ -10824,10 +10836,10 @@
10824
10836
  .file-preview-modal {
10825
10837
  width: 100vw;
10826
10838
  max-width: 100vw;
10827
- height: 100vh;
10828
- height: 100dvh;
10829
- max-height: 100vh;
10830
- max-height: 100dvh;
10839
+ /* 键盘弹起时跟随 --app-viewport-height (= visualViewport.height) 收缩,
10840
+ 否则 100dvh 不响应键盘, 编辑文本框会被键盘盖住。 */
10841
+ height: var(--app-viewport-height, 100dvh);
10842
+ max-height: var(--app-viewport-height, 100dvh);
10831
10843
  border-radius: 0;
10832
10844
  border: none;
10833
10845
  box-shadow: none;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@co0ontty/wand",
3
- "version": "1.25.1",
3
+ "version": "1.25.3",
4
4
  "description": "A web terminal for local CLI tools like Claude.",
5
5
  "type": "module",
6
6
  "bin": {