@co0ontty/wand 1.17.2 → 1.17.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.
@@ -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: {},
@@ -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,38 @@
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
+ return true;
4539
+ }
4540
+
4541
+ // Soft refresh the whole current view without losing page state:
4542
+ // - Replays terminal buffer to clear residue
4543
+ // - Clears chat render cache and forces a full rebuild
4544
+ // Used by the refresh button and by automatic triggers
4545
+ // (e.g. permission escalation appearing/disappearing).
4546
+ function softRefreshCurrentView() {
4547
+ softResyncTerminal();
4548
+ if (typeof resetChatRenderCache === "function") resetChatRenderCache();
4549
+ if (typeof scheduleChatRender === "function") scheduleChatRender(true);
4550
+ else if (typeof render === "function") render();
4551
+ }
4552
+
4553
+ function scheduleSoftResyncTerminal(delayMs) {
4554
+ if (state.softResyncTimer) clearTimeout(state.softResyncTimer);
4555
+ state.softResyncTimer = setTimeout(function() {
4556
+ state.softResyncTimer = null;
4557
+ softResyncTerminal();
4558
+ }, typeof delayMs === "number" ? delayMs : 150);
4559
+ }
4560
+
4496
4561
  function syncTerminalBuffer(sessionId, output, options) {
4497
4562
  if (!state.terminal) return false;
4498
4563
  var normalizedOutput = normalizeTerminalOutput(output || "");
@@ -5131,6 +5196,17 @@
5131
5196
  if (normalizedSnapshot.id === state.selectedId) {
5132
5197
  reconcileInteractiveState();
5133
5198
  updateTaskDisplay();
5199
+ // Escalation/permission toggles are the common trigger for CSI cursor-jump
5200
+ // redraw sequences from Claude CLI. When they appear or dismiss, schedule a
5201
+ // debounced terminal resync so residual DOM rows get cleaned up automatically
5202
+ // — same fix the user used to have to reach for via the refresh button.
5203
+ var prevEsc = prevSession && prevSession.pendingEscalation ? 1 : 0;
5204
+ var nextEsc = updatedSession && updatedSession.pendingEscalation ? 1 : 0;
5205
+ var prevBlocked = prevSession && prevSession.permissionBlocked ? 1 : 0;
5206
+ var nextBlocked = updatedSession && updatedSession.permissionBlocked ? 1 : 0;
5207
+ if (prevEsc !== nextEsc || prevBlocked !== nextBlocked) {
5208
+ scheduleSoftResyncTerminal(200);
5209
+ }
5134
5210
  }
5135
5211
  // When a session transitions to a non-running state, try flushing cross-session queue
5136
5212
  if (normalizedSnapshot.status && normalizedSnapshot.status !== "running" && state.crossSessionQueue.length > 0) {
@@ -6274,6 +6350,9 @@
6274
6350
  defaultModel: (document.getElementById("cfg-default-model") || {}).value || "",
6275
6351
  };
6276
6352
 
6353
+ var previousDefaultModel = (state.config && state.config.defaultModel) || "";
6354
+ var nextDefaultModel = body.defaultModel || "";
6355
+
6277
6356
  fetch("/api/settings/config", {
6278
6357
  method: "POST",
6279
6358
  headers: { "Content-Type": "application/json" },
@@ -6292,6 +6371,15 @@
6292
6371
  }
6293
6372
  msgEl.classList.remove("hidden");
6294
6373
  }
6374
+ if (!data || !data.error) {
6375
+ if (state.config) state.config.defaultModel = nextDefaultModel;
6376
+ state.configDefaultModel = nextDefaultModel;
6377
+ if (nextDefaultModel !== previousDefaultModel) {
6378
+ state.chatModel = "";
6379
+ try { localStorage.removeItem("wand-chat-model"); } catch (e) {}
6380
+ syncComposerModelSelect(getSelectedSession());
6381
+ }
6382
+ }
6295
6383
  })
6296
6384
  .catch(function() {
6297
6385
  if (msgEl) {
@@ -13113,6 +13201,65 @@
13113
13201
  return deduped.join(newline);
13114
13202
  }
13115
13203
 
13204
+ function parseMarkdownTables(source) {
13205
+ var NL = "\n";
13206
+ var lines = source.split(NL);
13207
+ var out = [];
13208
+ var i = 0;
13209
+
13210
+ function splitRow(line) {
13211
+ var s = line.trim();
13212
+ if (s.charAt(0) === "|") s = s.slice(1);
13213
+ if (s.charAt(s.length - 1) === "|") s = s.slice(0, -1);
13214
+ return s.split("|");
13215
+ }
13216
+ function styleAttr(a) { return a ? ' style="text-align:' + a + '"' : ""; }
13217
+ function buildTable(headers, aligns, rows) {
13218
+ var thead = "<thead><tr>" + headers.map(function(c, idx) {
13219
+ return "<th" + styleAttr(aligns[idx]) + ">" + c.trim() + "</th>";
13220
+ }).join("") + "</tr></thead>";
13221
+ var tbody = rows.length ? ("<tbody>" + rows.map(function(r) {
13222
+ return "<tr>" + r.map(function(c, idx) {
13223
+ return "<td" + styleAttr(aligns[idx]) + ">" + c.trim() + "</td>";
13224
+ }).join("") + "</tr>";
13225
+ }).join("") + "</tbody>") : "";
13226
+ return '<div class="md-table-wrap"><table class="md-table">' + thead + tbody + "</table></div>";
13227
+ }
13228
+
13229
+ while (i < lines.length) {
13230
+ var header = lines[i];
13231
+ if (header.indexOf("|") !== -1 && i + 1 < lines.length) {
13232
+ var sep = lines[i + 1].trim();
13233
+ if (/^\|?\s*:?-+:?(\s*\|\s*:?-+:?)+\s*\|?$/.test(sep)) {
13234
+ var headers = splitRow(header);
13235
+ var aligns = splitRow(sep).map(function(c) {
13236
+ var t = c.trim();
13237
+ var L = t.charAt(0) === ":";
13238
+ var R = t.charAt(t.length - 1) === ":";
13239
+ if (L && R) return "center";
13240
+ if (R) return "right";
13241
+ if (L) return "left";
13242
+ return "";
13243
+ });
13244
+ var rows = [];
13245
+ var j = i + 2;
13246
+ while (j < lines.length) {
13247
+ var trimmed = lines[j].trim();
13248
+ if (!trimmed || trimmed.indexOf("|") === -1) break;
13249
+ rows.push(splitRow(lines[j]));
13250
+ j += 1;
13251
+ }
13252
+ out.push("", buildTable(headers, aligns, rows), "");
13253
+ i = j;
13254
+ continue;
13255
+ }
13256
+ }
13257
+ out.push(header);
13258
+ i += 1;
13259
+ }
13260
+ return out.join(NL);
13261
+ }
13262
+
13116
13263
  function renderMarkdown(text) {
13117
13264
  if (!text) return "";
13118
13265
 
@@ -13237,6 +13384,7 @@
13237
13384
  result = replaceLinePrefix(result, "- ", '<li>', '</li>');
13238
13385
  result = replaceLinePrefix(result, "* ", '<li>', '</li>');
13239
13386
  result = replaceOrderedList(result);
13387
+ result = parseMarkdownTables(result);
13240
13388
 
13241
13389
  var lines = result.split(newline);
13242
13390
  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.4",
4
4
  "description": "A web terminal for local CLI tools like Claude.",
5
5
  "type": "module",
6
6
  "bin": {