@co0ontty/wand 1.17.2 → 1.17.5

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;
@@ -201,6 +201,7 @@
201
201
  claudeHistoryLoaded: false,
202
202
  claudeHistoryExpanded: true,
203
203
  claudeHistoryExpandedDirs: {},
204
+ archivedExpanded: false,
204
205
  sessionsManageMode: false,
205
206
  selectedSessionIds: {},
206
207
  selectedClaudeHistoryIds: {},
@@ -1256,7 +1257,7 @@
1256
1257
  '<button id="attach-btn" class="btn-circle btn-circle-attach" type="button" title="附加文件">' +
1257
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>' +
1258
1259
  '</button>' +
1259
- '<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">' +
1260
1261
  '<select id="chat-mode-select" class="chat-mode-select" title="仅对新建会话生效">' +
1261
1262
  renderModeOptions(preferredTool, composerMode) +
1262
1263
  '</select>' +
@@ -1733,7 +1734,7 @@
1733
1734
  groups.push(renderRecentGroup(activeSessions, recentHistorySessions));
1734
1735
  }
1735
1736
  if (archivedSessions.length > 0) {
1736
- groups.push(renderSessionGroup("已归档", archivedSessions, "sessions"));
1737
+ groups.push(renderArchivedGroup(archivedSessions));
1737
1738
  }
1738
1739
  groups.push(renderClaudeHistorySection());
1739
1740
  if (activeSessions.length === 0 && archivedSessions.length === 0 && recentHistorySessions.length === 0) {
@@ -1754,12 +1755,17 @@
1754
1755
  var historyCount = getSelectedClaudeHistoryIds().length;
1755
1756
  var totalCount = sessionCount + historyCount;
1756
1757
  var hasAny = totalCount > 0;
1758
+ var selectable = countSelectableItems();
1759
+ var allSelected = selectable > 0 && totalCount >= selectable;
1760
+ var selectAllLabel = allSelected ? "取消全选" : "全选";
1761
+ var selectAllAction = allSelected ? "clear-selection" : "select-all-visible";
1762
+ var selectAllDisabled = selectable === 0 ? ' disabled' : '';
1757
1763
 
1758
1764
  return '<div class="session-manage-bar active">' +
1759
1765
  '<div class="session-manage-summary">已选择 ' + totalCount + ' 项</div>' +
1760
1766
  '<div class="session-manage-actions">' +
1761
- '<button class="session-manage-btn" data-action="select-all-visible" type="button">全选</button>' +
1762
- '<button class="session-manage-btn" data-action="clear-selection" type="button">清空</button>' +
1767
+ '<button class="session-manage-btn" data-action="' + selectAllAction + '" type="button"' + selectAllDisabled + '>' + selectAllLabel + '</button>' +
1768
+ '<button class="session-manage-btn" data-action="clear-selection" type="button"' + (hasAny ? '' : ' disabled') + '>清空</button>' +
1763
1769
  '<button class="session-manage-btn danger" data-action="delete-selected" type="button"' + (hasAny ? '' : ' disabled') + '>删除所选</button>' +
1764
1770
  '<button class="session-manage-btn" data-action="toggle-manage-mode" type="button">完成</button>' +
1765
1771
  '</div>' +
@@ -1773,6 +1779,20 @@
1773
1779
  '</section>';
1774
1780
  }
1775
1781
 
1782
+ function renderArchivedGroup(archivedSessions) {
1783
+ var expanded = !!state.archivedExpanded;
1784
+ var chevron = expanded ? "&#9662;" : "&#9656;";
1785
+ var header = '<div class="session-group-title claude-history-toggle" data-action="toggle-archived-group">' +
1786
+ '<span class="chevron">' + chevron + '</span> 已归档 ' +
1787
+ '<span class="history-count">' + archivedSessions.length + '</span>' +
1788
+ '</div>';
1789
+ if (!expanded) {
1790
+ return '<section class="session-group">' + header + '</section>';
1791
+ }
1792
+ var items = archivedSessions.map(function(session) { return renderSessionItem(session, "sessions"); }).join("");
1793
+ return '<section class="session-group">' + header + items + '</section>';
1794
+ }
1795
+
1776
1796
  function renderRecentGroup(activeSessions, recentHistorySessions) {
1777
1797
  var html = '<section class="session-group">' +
1778
1798
  '<div class="session-group-title">最近</div>';
@@ -1864,11 +1884,19 @@
1864
1884
  updateSessionsList();
1865
1885
  }
1866
1886
 
1887
+ function getSelectableSessions() {
1888
+ return state.sessions.filter(function(session) {
1889
+ return session.archived || !session.resumedToSessionId;
1890
+ });
1891
+ }
1892
+
1893
+ function countSelectableItems() {
1894
+ return getSelectableSessions().length + getVisibleClaudeHistorySessions().length;
1895
+ }
1896
+
1867
1897
  function selectAllVisibleItems() {
1868
1898
  var nextSessionIds = {};
1869
- state.sessions.filter(function(session) {
1870
- return !session.resumedToSessionId;
1871
- }).forEach(function(session) {
1899
+ getSelectableSessions().forEach(function(session) {
1872
1900
  nextSessionIds[session.id] = true;
1873
1901
  });
1874
1902
  var nextHistoryIds = {};
@@ -2507,15 +2535,8 @@
2507
2535
  // Ordered lists
2508
2536
  escaped = escaped.replace(/^\d+\.\s+(.*)$/gm, '<li>$1</li>');
2509
2537
 
2510
- // Tables
2511
- escaped = escaped.replace(/\|(.+)\|/g, function(match) {
2512
- var cells = match.split("|").slice(1, -1);
2513
- if (cells.every(function(c) { return /^[\-:]+$/.test(c.trim()); })) {
2514
- return "";
2515
- }
2516
- return '<tr>' + cells.map(function(c) { return '<td>' + c.trim() + '</td>'; }).join("") + '</tr>';
2517
- });
2518
- escaped = escaped.replace(/(<tr>.*<\/tr>\n?)+/g, '<table>$&</table>');
2538
+ // Tables (GFM)
2539
+ escaped = parseMarkdownTables(escaped);
2519
2540
 
2520
2541
  // Paragraphs
2521
2542
  var paragraphs = escaped.split(/\n{2,}/);
@@ -3635,7 +3656,16 @@
3635
3656
  if (scaleDownBtn) scaleDownBtn.addEventListener("click", function() { adjustTerminalScale(-0.25); });
3636
3657
  if (scaleUpBtn) scaleUpBtn.addEventListener("click", function() { adjustTerminalScale(0.25); });
3637
3658
  var pageRefreshBtn = document.getElementById("page-refresh-btn");
3638
- if (pageRefreshBtn) pageRefreshBtn.addEventListener("click", function() { location.reload(); });
3659
+ if (pageRefreshBtn) pageRefreshBtn.addEventListener("click", function(ev) {
3660
+ // Soft refresh: replay terminal buffer + rebuild chat view.
3661
+ // Fixes residual DOM from CSI cursor-jump sequences without losing page state.
3662
+ // Hold Shift to force a full page reload as an escape hatch.
3663
+ if (ev && ev.shiftKey) {
3664
+ location.reload();
3665
+ return;
3666
+ }
3667
+ softRefreshCurrentView();
3668
+ });
3639
3669
  var jumpBottomBtn = document.getElementById("terminal-jump-bottom");
3640
3670
  if (jumpBottomBtn) jumpBottomBtn.addEventListener("click", function() {
3641
3671
  maybeScrollTerminalToBottom("force");
@@ -4125,6 +4155,9 @@
4125
4155
  }
4126
4156
  } else if (actionButton.dataset.action === "clear-all-history") {
4127
4157
  clearAllClaudeHistory();
4158
+ } else if (actionButton.dataset.action === "toggle-archived-group") {
4159
+ state.archivedExpanded = !state.archivedExpanded;
4160
+ updateSessionsList();
4128
4161
  } else if (actionButton.dataset.action === "resume" && actionButton.dataset.sessionId) {
4129
4162
  handleResumeAction(actionButton);
4130
4163
  } else if (actionButton.dataset.action === "resume-history" && actionButton.dataset.claudeSessionId) {
@@ -4493,6 +4526,42 @@
4493
4526
  state.terminal.reset();
4494
4527
  }
4495
4528
 
4529
+ // Soft resync terminal: reset WASM grid and replay full output buffer.
4530
+ // Clears any stale DOM rows left over from CSI cursor-jump sequences
4531
+ // (e.g. Claude permission menus redrawing in place while user holds arrow keys).
4532
+ function softResyncTerminal() {
4533
+ if (!state.terminal || !state.terminalOutput) return false;
4534
+ resetTerminal();
4535
+ state.terminal.write(state.terminalOutput);
4536
+ state.lastTerminalResyncAt = Date.now();
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");
4542
+ return true;
4543
+ }
4544
+
4545
+ // Soft refresh the whole current view without losing page state:
4546
+ // - Replays terminal buffer to clear residue
4547
+ // - Clears chat render cache and forces a full rebuild
4548
+ // Used by the refresh button and by automatic triggers
4549
+ // (e.g. permission escalation appearing/disappearing).
4550
+ function softRefreshCurrentView() {
4551
+ softResyncTerminal();
4552
+ if (typeof resetChatRenderCache === "function") resetChatRenderCache();
4553
+ if (typeof scheduleChatRender === "function") scheduleChatRender(true);
4554
+ else if (typeof render === "function") render();
4555
+ }
4556
+
4557
+ function scheduleSoftResyncTerminal(delayMs) {
4558
+ if (state.softResyncTimer) clearTimeout(state.softResyncTimer);
4559
+ state.softResyncTimer = setTimeout(function() {
4560
+ state.softResyncTimer = null;
4561
+ softResyncTerminal();
4562
+ }, typeof delayMs === "number" ? delayMs : 150);
4563
+ }
4564
+
4496
4565
  function syncTerminalBuffer(sessionId, output, options) {
4497
4566
  if (!state.terminal) return false;
4498
4567
  var normalizedOutput = normalizeTerminalOutput(output || "");
@@ -4643,6 +4712,10 @@
4643
4712
  initTerminalResizeHandle();
4644
4713
  observeTerminalResize();
4645
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");
4646
4719
  }).catch(function(err) {
4647
4720
  state.terminalInitializing = false;
4648
4721
  console.error("[wand] wterm init failed:", err);
@@ -5131,6 +5204,17 @@
5131
5204
  if (normalizedSnapshot.id === state.selectedId) {
5132
5205
  reconcileInteractiveState();
5133
5206
  updateTaskDisplay();
5207
+ // Escalation/permission toggles are the common trigger for CSI cursor-jump
5208
+ // redraw sequences from Claude CLI. When they appear or dismiss, schedule a
5209
+ // debounced terminal resync so residual DOM rows get cleaned up automatically
5210
+ // — same fix the user used to have to reach for via the refresh button.
5211
+ var prevEsc = prevSession && prevSession.pendingEscalation ? 1 : 0;
5212
+ var nextEsc = updatedSession && updatedSession.pendingEscalation ? 1 : 0;
5213
+ var prevBlocked = prevSession && prevSession.permissionBlocked ? 1 : 0;
5214
+ var nextBlocked = updatedSession && updatedSession.permissionBlocked ? 1 : 0;
5215
+ if (prevEsc !== nextEsc || prevBlocked !== nextBlocked) {
5216
+ scheduleSoftResyncTerminal(200);
5217
+ }
5134
5218
  }
5135
5219
  // When a session transitions to a non-running state, try flushing cross-session queue
5136
5220
  if (normalizedSnapshot.status && normalizedSnapshot.status !== "running" && state.crossSessionQueue.length > 0) {
@@ -5444,11 +5528,9 @@
5444
5528
 
5445
5529
  if (state.terminal && id === state.selectedId && data.output !== undefined) {
5446
5530
  syncTerminalBuffer(id, data.output, { mode: "append" });
5447
- if (state.terminal.remeasure) {
5448
- requestAnimationFrame(function() {
5449
- if (state.terminal) state.terminal.remeasure();
5450
- });
5451
- }
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");
5452
5534
  }
5453
5535
 
5454
5536
  var selectedSession = state.sessions.find(function(s) { return s.id === id; });
@@ -6274,6 +6356,9 @@
6274
6356
  defaultModel: (document.getElementById("cfg-default-model") || {}).value || "",
6275
6357
  };
6276
6358
 
6359
+ var previousDefaultModel = (state.config && state.config.defaultModel) || "";
6360
+ var nextDefaultModel = body.defaultModel || "";
6361
+
6277
6362
  fetch("/api/settings/config", {
6278
6363
  method: "POST",
6279
6364
  headers: { "Content-Type": "application/json" },
@@ -6292,6 +6377,15 @@
6292
6377
  }
6293
6378
  msgEl.classList.remove("hidden");
6294
6379
  }
6380
+ if (!data || !data.error) {
6381
+ if (state.config) state.config.defaultModel = nextDefaultModel;
6382
+ state.configDefaultModel = nextDefaultModel;
6383
+ if (nextDefaultModel !== previousDefaultModel) {
6384
+ state.chatModel = "";
6385
+ try { localStorage.removeItem("wand-chat-model"); } catch (e) {}
6386
+ syncComposerModelSelect(getSelectedSession());
6387
+ }
6388
+ }
6295
6389
  })
6296
6390
  .catch(function() {
6297
6391
  if (msgEl) {
@@ -7796,6 +7890,10 @@
7796
7890
  }
7797
7891
  applyCurrentView();
7798
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");
7799
7897
  }
7800
7898
 
7801
7899
 
@@ -10001,6 +10099,15 @@
10001
10099
  state.visualViewportHandler = function() { scheduleTerminalResize(true); };
10002
10100
  window.visualViewport.addEventListener("resize", state.visualViewportHandler);
10003
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);
10004
10111
  requestAnimationFrame(function() { scheduleTerminalResize(true); });
10005
10112
  }
10006
10113
 
@@ -10010,17 +10117,14 @@
10010
10117
  if (!state.terminal || state.currentView !== "terminal" || document.hidden) return;
10011
10118
  var selectedSession = state.sessions.find(function(s) { return s.id === state.selectedId; });
10012
10119
  if (!selectedSession || selectedSession.sessionKind === "structured") return;
10013
- // Lightweight remeasure every 5s
10014
- if (state.terminal.remeasure) state.terminal.remeasure();
10015
- // 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.
10016
10123
  var now = Date.now();
10017
10124
  var chunkPause = state.lastChunkAt > 0 && (now - state.lastChunkAt > 300);
10018
10125
  var resyncDue = (now - state.lastTerminalResyncAt) > 30000;
10019
10126
  if (resyncDue && (chunkPause || selectedSession.status !== "running") && state.terminalOutput) {
10020
- state.lastTerminalResyncAt = now;
10021
- state.terminal.reset();
10022
- state.terminal.write(state.terminalOutput);
10023
- maybeScrollTerminalToBottom("output");
10127
+ softResyncTerminal();
10024
10128
  }
10025
10129
  }, 5000);
10026
10130
  }
@@ -10050,6 +10154,14 @@
10050
10154
  window.visualViewport.removeEventListener("resize", state.visualViewportHandler);
10051
10155
  state.visualViewportHandler = null;
10052
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
+ }
10053
10165
  [["mousemove", "resizeMouseMove"], ["mouseup", "resizeMouseUp"],
10054
10166
  ["touchmove", "resizeTouchMove"], ["touchend", "resizeTouchEnd"]
10055
10167
  ].forEach(function(pair) {
@@ -10113,10 +10225,30 @@
10113
10225
  }
10114
10226
  }
10115
10227
 
10116
- function ensureTerminalFit() {
10117
- if (!state.terminal) return;
10118
- maybeScrollTerminalToBottom("resize");
10119
- 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;
10120
10252
  }
10121
10253
 
10122
10254
  function scheduleTerminalResize(immediate) {
@@ -10181,10 +10313,9 @@
10181
10313
  // Flush pending messages after reconnection
10182
10314
  flushPendingMessages();
10183
10315
  // Re-fit terminal on reconnect — the viewport may have changed
10184
- // while disconnected, and the PTY needs up-to-date dimensions.
10185
- if (state.terminal) {
10186
- sendTerminalResize(state.terminal.cols, state.terminal.rows);
10187
- }
10316
+ // while disconnected, so remeasure against real container size
10317
+ // rather than sending stale cols/rows from before the disconnect.
10318
+ ensureTerminalFit("ws-reconnect");
10188
10319
  };
10189
10320
 
10190
10321
  ws.onmessage = function(event) {
@@ -13113,6 +13244,65 @@
13113
13244
  return deduped.join(newline);
13114
13245
  }
13115
13246
 
13247
+ function parseMarkdownTables(source) {
13248
+ var NL = "\n";
13249
+ var lines = source.split(NL);
13250
+ var out = [];
13251
+ var i = 0;
13252
+
13253
+ function splitRow(line) {
13254
+ var s = line.trim();
13255
+ if (s.charAt(0) === "|") s = s.slice(1);
13256
+ if (s.charAt(s.length - 1) === "|") s = s.slice(0, -1);
13257
+ return s.split("|");
13258
+ }
13259
+ function styleAttr(a) { return a ? ' style="text-align:' + a + '"' : ""; }
13260
+ function buildTable(headers, aligns, rows) {
13261
+ var thead = "<thead><tr>" + headers.map(function(c, idx) {
13262
+ return "<th" + styleAttr(aligns[idx]) + ">" + c.trim() + "</th>";
13263
+ }).join("") + "</tr></thead>";
13264
+ var tbody = rows.length ? ("<tbody>" + rows.map(function(r) {
13265
+ return "<tr>" + r.map(function(c, idx) {
13266
+ return "<td" + styleAttr(aligns[idx]) + ">" + c.trim() + "</td>";
13267
+ }).join("") + "</tr>";
13268
+ }).join("") + "</tbody>") : "";
13269
+ return '<div class="md-table-wrap"><table class="md-table">' + thead + tbody + "</table></div>";
13270
+ }
13271
+
13272
+ while (i < lines.length) {
13273
+ var header = lines[i];
13274
+ if (header.indexOf("|") !== -1 && i + 1 < lines.length) {
13275
+ var sep = lines[i + 1].trim();
13276
+ if (/^\|?\s*:?-+:?(\s*\|\s*:?-+:?)+\s*\|?$/.test(sep)) {
13277
+ var headers = splitRow(header);
13278
+ var aligns = splitRow(sep).map(function(c) {
13279
+ var t = c.trim();
13280
+ var L = t.charAt(0) === ":";
13281
+ var R = t.charAt(t.length - 1) === ":";
13282
+ if (L && R) return "center";
13283
+ if (R) return "right";
13284
+ if (L) return "left";
13285
+ return "";
13286
+ });
13287
+ var rows = [];
13288
+ var j = i + 2;
13289
+ while (j < lines.length) {
13290
+ var trimmed = lines[j].trim();
13291
+ if (!trimmed || trimmed.indexOf("|") === -1) break;
13292
+ rows.push(splitRow(lines[j]));
13293
+ j += 1;
13294
+ }
13295
+ out.push("", buildTable(headers, aligns, rows), "");
13296
+ i = j;
13297
+ continue;
13298
+ }
13299
+ }
13300
+ out.push(header);
13301
+ i += 1;
13302
+ }
13303
+ return out.join(NL);
13304
+ }
13305
+
13116
13306
  function renderMarkdown(text) {
13117
13307
  if (!text) return "";
13118
13308
 
@@ -13237,6 +13427,7 @@
13237
13427
  result = replaceLinePrefix(result, "- ", '<li>', '</li>');
13238
13428
  result = replaceLinePrefix(result, "* ", '<li>', '</li>');
13239
13429
  result = replaceOrderedList(result);
13430
+ result = parseMarkdownTables(result);
13240
13431
 
13241
13432
  var lines = result.split(newline);
13242
13433
  var grouped = [];
@@ -4463,6 +4463,33 @@
4463
4463
  padding: 2px 5px;
4464
4464
  border-radius: 4px;
4465
4465
  }
4466
+ .markdown-content .md-table-wrap {
4467
+ margin: 12px 0;
4468
+ overflow-x: auto;
4469
+ border: 1px solid var(--border);
4470
+ border-radius: var(--radius-sm);
4471
+ -webkit-overflow-scrolling: touch;
4472
+ }
4473
+ .markdown-content .md-table {
4474
+ border-collapse: collapse;
4475
+ width: 100%;
4476
+ font-size: 0.8125rem;
4477
+ white-space: normal;
4478
+ }
4479
+ .markdown-content .md-table th,
4480
+ .markdown-content .md-table td {
4481
+ border: 1px solid var(--border);
4482
+ padding: 6px 10px;
4483
+ text-align: left;
4484
+ vertical-align: top;
4485
+ }
4486
+ .markdown-content .md-table th {
4487
+ background: rgba(150, 118, 85, 0.1);
4488
+ font-weight: 600;
4489
+ }
4490
+ .markdown-content .md-table tbody tr:nth-child(even) {
4491
+ background: rgba(150, 118, 85, 0.04);
4492
+ }
4466
4493
  .markdown-content .code-block {
4467
4494
  margin: 12px 0;
4468
4495
  border-radius: var(--radius-md);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@co0ontty/wand",
3
- "version": "1.17.2",
3
+ "version": "1.17.5",
4
4
  "description": "A web terminal for local CLI tools like Claude.",
5
5
  "type": "module",
6
6
  "bin": {