@co0ontty/wand 1.17.4 → 1.17.6

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/server.js CHANGED
@@ -60,13 +60,31 @@ async function checkNpmLatestVersion(forceRefresh = false) {
60
60
  };
61
61
  }
62
62
  function compareSemver(a, b) {
63
- const pa = a.split(".").map(Number);
64
- const pb = b.split(".").map(Number);
63
+ const parse = (v) => {
64
+ const [main, ...rest] = v.split("-");
65
+ const pre = rest.join("-");
66
+ const mainParts = main.split(".").map((n) => Number(n) || 0);
67
+ return { mainParts, pre };
68
+ };
69
+ const pa = parse(a);
70
+ const pb = parse(b);
65
71
  for (let i = 0; i < 3; i++) {
66
- const diff = (pa[i] || 0) - (pb[i] || 0);
72
+ const diff = (pa.mainParts[i] || 0) - (pb.mainParts[i] || 0);
67
73
  if (diff !== 0)
68
74
  return diff;
69
75
  }
76
+ // Main version equal — apply semver prerelease rule: no prerelease > with prerelease.
77
+ if (!pa.pre && pb.pre)
78
+ return 1;
79
+ if (pa.pre && !pb.pre)
80
+ return -1;
81
+ if (!pa.pre && !pb.pre)
82
+ return 0;
83
+ // Both have prerelease: lexical compare handles debug.MMDDHHMM ordering.
84
+ if (pa.pre < pb.pre)
85
+ return -1;
86
+ if (pa.pre > pb.pre)
87
+ return 1;
70
88
  return 0;
71
89
  }
72
90
  let cachedGitHubApk = null;
@@ -1257,7 +1257,7 @@
1257
1257
  '<button id="attach-btn" class="btn-circle btn-circle-attach" type="button" title="附加文件">' +
1258
1258
  '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>' +
1259
1259
  '</button>' +
1260
- '<input type="file" id="file-upload-input" multiple style="display:none">' +
1260
+ '<input type="file" id="file-upload-input" multiple style="position:absolute;width:1px;height:1px;opacity:0;overflow:hidden;clip:rect(0,0,0,0);pointer-events:none">' +
1261
1261
  '<select id="chat-mode-select" class="chat-mode-select" title="仅对新建会话生效">' +
1262
1262
  renderModeOptions(preferredTool, composerMode) +
1263
1263
  '</select>' +
@@ -4535,6 +4535,10 @@
4535
4535
  state.terminal.write(state.terminalOutput);
4536
4536
  state.lastTerminalResyncAt = Date.now();
4537
4537
  maybeScrollTerminalToBottom("output");
4538
+ // Remeasure against real container: the refresh button used to only
4539
+ // reset+write, so a stale cols/rows (set at mount time with hidden
4540
+ // container) would survive the refresh and keep wrapping output wrong.
4541
+ ensureTerminalFit("refresh");
4538
4542
  return true;
4539
4543
  }
4540
4544
 
@@ -4708,6 +4712,10 @@
4708
4712
  initTerminalResizeHandle();
4709
4713
  observeTerminalResize();
4710
4714
  startTerminalHealthCheck();
4715
+ // Container may have been hidden / zero-width at construction
4716
+ // time (hard-coded 120x36). Remeasure against the real container
4717
+ // so wterm reflows the just-written history to the correct cols.
4718
+ ensureTerminalFit("mount");
4711
4719
  }).catch(function(err) {
4712
4720
  state.terminalInitializing = false;
4713
4721
  console.error("[wand] wterm init failed:", err);
@@ -5520,11 +5528,9 @@
5520
5528
 
5521
5529
  if (state.terminal && id === state.selectedId && data.output !== undefined) {
5522
5530
  syncTerminalBuffer(id, data.output, { mode: "append" });
5523
- if (state.terminal.remeasure) {
5524
- requestAnimationFrame(function() {
5525
- if (state.terminal) state.terminal.remeasure();
5526
- });
5527
- }
5531
+ // Session-switch / history replay: force a real fit so wterm
5532
+ // reflows the just-written output against the real container.
5533
+ ensureTerminalFit("session-switch");
5528
5534
  }
5529
5535
 
5530
5536
  var selectedSession = state.sessions.find(function(s) { return s.id === id; });
@@ -7884,6 +7890,10 @@
7884
7890
  }
7885
7891
  applyCurrentView();
7886
7892
  focusInputBox(true);
7893
+ // Container just flipped from hidden -> visible (or geometry changed
7894
+ // because chat/terminal panels swapped). Refit now so the terminal
7895
+ // picks up the real cols/rows instead of keeping the stale ones.
7896
+ if (!structured) ensureTerminalFit("view-switch");
7887
7897
  }
7888
7898
 
7889
7899
 
@@ -10089,6 +10099,15 @@
10089
10099
  state.visualViewportHandler = function() { scheduleTerminalResize(true); };
10090
10100
  window.visualViewport.addEventListener("resize", state.visualViewportHandler);
10091
10101
  }
10102
+ // Page returning from background: container dimensions may have
10103
+ // drifted (PWA standalone, tab switch, iOS address-bar toggle).
10104
+ state.visibilityHandler = function() {
10105
+ if (!document.hidden) ensureTerminalFit("visibility");
10106
+ };
10107
+ document.addEventListener("visibilitychange", state.visibilityHandler);
10108
+ // Mobile device rotation — large geometry change.
10109
+ state.orientationHandler = function() { ensureTerminalFit("orientation"); };
10110
+ window.addEventListener("orientationchange", state.orientationHandler);
10092
10111
  requestAnimationFrame(function() { scheduleTerminalResize(true); });
10093
10112
  }
10094
10113
 
@@ -10098,17 +10117,14 @@
10098
10117
  if (!state.terminal || state.currentView !== "terminal" || document.hidden) return;
10099
10118
  var selectedSession = state.sessions.find(function(s) { return s.id === state.selectedId; });
10100
10119
  if (!selectedSession || selectedSession.sessionKind === "structured") return;
10101
- // Lightweight remeasure every 5s
10102
- if (state.terminal.remeasure) state.terminal.remeasure();
10103
- // Full re-sync every 30s during output pauses
10120
+ // Lightweight fit every 5s: gated + double-rAF + remeasure.
10121
+ ensureTerminalFit("health");
10122
+ // Full re-sync every 30s during output pauses — also refits.
10104
10123
  var now = Date.now();
10105
10124
  var chunkPause = state.lastChunkAt > 0 && (now - state.lastChunkAt > 300);
10106
10125
  var resyncDue = (now - state.lastTerminalResyncAt) > 30000;
10107
10126
  if (resyncDue && (chunkPause || selectedSession.status !== "running") && state.terminalOutput) {
10108
- state.lastTerminalResyncAt = now;
10109
- state.terminal.reset();
10110
- state.terminal.write(state.terminalOutput);
10111
- maybeScrollTerminalToBottom("output");
10127
+ softResyncTerminal();
10112
10128
  }
10113
10129
  }, 5000);
10114
10130
  }
@@ -10138,6 +10154,14 @@
10138
10154
  window.visualViewport.removeEventListener("resize", state.visualViewportHandler);
10139
10155
  state.visualViewportHandler = null;
10140
10156
  }
10157
+ if (state.visibilityHandler) {
10158
+ document.removeEventListener("visibilitychange", state.visibilityHandler);
10159
+ state.visibilityHandler = null;
10160
+ }
10161
+ if (state.orientationHandler) {
10162
+ window.removeEventListener("orientationchange", state.orientationHandler);
10163
+ state.orientationHandler = null;
10164
+ }
10141
10165
  [["mousemove", "resizeMouseMove"], ["mouseup", "resizeMouseUp"],
10142
10166
  ["touchmove", "resizeTouchMove"], ["touchend", "resizeTouchEnd"]
10143
10167
  ].forEach(function(pair) {
@@ -10201,10 +10225,30 @@
10201
10225
  }
10202
10226
  }
10203
10227
 
10204
- function ensureTerminalFit() {
10205
- if (!state.terminal) return;
10206
- maybeScrollTerminalToBottom("resize");
10207
- sendTerminalResize(state.terminal.cols, state.terminal.rows);
10228
+ // Unified entry point for re-fitting the xterm grid to its container.
10229
+ // Why: wterm's `autoResize` ResizeObserver only fires on subsequent
10230
+ // container size changes. If the terminal is mounted or written to
10231
+ // while the container is hidden/zero-width, cols/rows stay wrong and
10232
+ // new output renders with broken wrapping (content visually piles at
10233
+ // the top). Call this after any layout change that might have altered
10234
+ // container geometry (mount, session switch, view switch, refresh).
10235
+ function ensureTerminalFit(reason) {
10236
+ if (!state.terminal) return false;
10237
+ var el = document.getElementById("output");
10238
+ if (!el || el.offsetWidth === 0 || el.offsetHeight === 0) return false;
10239
+ requestAnimationFrame(function() {
10240
+ requestAnimationFrame(function() {
10241
+ if (!state.terminal) return;
10242
+ if (typeof state.terminal.remeasure === "function") {
10243
+ state.terminal.remeasure();
10244
+ }
10245
+ sendTerminalResize(state.terminal.cols, state.terminal.rows);
10246
+ if (state.terminalAutoFollow || isTerminalNearBottom()) {
10247
+ maybeScrollTerminalToBottom("resize");
10248
+ }
10249
+ });
10250
+ });
10251
+ return true;
10208
10252
  }
10209
10253
 
10210
10254
  function scheduleTerminalResize(immediate) {
@@ -10269,10 +10313,9 @@
10269
10313
  // Flush pending messages after reconnection
10270
10314
  flushPendingMessages();
10271
10315
  // Re-fit terminal on reconnect — the viewport may have changed
10272
- // while disconnected, and the PTY needs up-to-date dimensions.
10273
- if (state.terminal) {
10274
- sendTerminalResize(state.terminal.cols, state.terminal.rows);
10275
- }
10316
+ // while disconnected, so remeasure against real container size
10317
+ // rather than sending stale cols/rows from before the disconnect.
10318
+ ensureTerminalFit("ws-reconnect");
10276
10319
  };
10277
10320
 
10278
10321
  ws.onmessage = function(event) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@co0ontty/wand",
3
- "version": "1.17.4",
3
+ "version": "1.17.6",
4
4
  "description": "A web terminal for local CLI tools like Claude.",
5
5
  "type": "module",
6
6
  "bin": {