@co0ontty/wand 1.21.14 → 1.21.16

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.
@@ -328,29 +328,81 @@
328
328
  // 先驱动跨视图的运行指示器(顶部进度条/徽章计时/气泡呼吸条)
329
329
  updateRunningIndicators(session);
330
330
 
331
- // 旧的 .structured-status-bar(在 composer-top-row 里独立的小条)
332
- // 已经合并进 #todo-progress 顶部状态条。这里只清理可能残留的旧节点 +
333
- // 维护 composer in-flight 的发光态 + 同步 _statusBarStartTime 给
334
- // 顶部徽章计时复用。
335
- var legacy = document.querySelector(".structured-status-bar");
336
- if (legacy) legacy.remove();
337
-
331
+ // Status bar now lives in .composer-top-row alongside the todo-progress collapse bar
332
+ var topRow = document.querySelector(".composer-top-row");
333
+ var existing = document.querySelector(".structured-status-bar");
338
334
  var composer = document.querySelector(".input-composer");
339
335
  if (!session || !isStructuredSession(session)) {
336
+ if (existing) existing.remove();
340
337
  if (composer) composer.classList.remove("in-flight");
341
- if (_statusBarTimerId) { clearInterval(_statusBarTimerId); _statusBarTimerId = null; }
342
- _statusBarStartTime = 0;
338
+ clearInterval(_statusBarTimerId);
339
+ _statusBarTimerId = null;
343
340
  return;
344
341
  }
345
342
 
346
- var isInFlight = !!(session.structuredState && session.structuredState.inFlight);
343
+ var isInFlight = session.structuredState && session.structuredState.inFlight;
344
+
347
345
  if (isInFlight) {
348
- if (!_statusBarStartTime) _statusBarStartTime = Date.now();
346
+ // Start timer if not already running
347
+ if (!_statusBarTimerId) {
348
+ _statusBarStartTime = Date.now();
349
+ }
350
+
351
+ // Add glow to input composer
349
352
  if (composer) composer.classList.add("in-flight");
353
+
354
+ if (!existing && topRow) {
355
+ var bar = document.createElement("div");
356
+ bar.className = "structured-status-bar";
357
+ bar.innerHTML =
358
+ '<span class="status-bar-dot"></span>' +
359
+ '<span class="status-bar-label">回复中</span>' +
360
+ '<span class="status-bar-timer">0.0s</span>';
361
+ // Append as last child of the top row so it sits to the right of the todo bar
362
+ topRow.appendChild(bar);
363
+ existing = bar;
364
+ } else if (existing && existing.classList.contains("completed")) {
365
+ // Was completed, now in-flight again — reset
366
+ existing.classList.remove("completed");
367
+ existing.style.animation = "none";
368
+ existing.querySelector(".status-bar-label").textContent = "回复中";
369
+ var dot = existing.querySelector(".status-bar-dot");
370
+ if (dot) dot.style.display = "";
371
+ _statusBarStartTime = Date.now();
372
+ }
373
+
374
+ // Start interval to update timer
375
+ if (!_statusBarTimerId) {
376
+ _statusBarTimerId = setInterval(function() {
377
+ var bar = document.querySelector(".structured-status-bar:not(.completed)");
378
+ if (!bar) { clearInterval(_statusBarTimerId); _statusBarTimerId = null; return; }
379
+ var elapsed = ((Date.now() - _statusBarStartTime) / 1000).toFixed(1);
380
+ var timerEl = bar.querySelector(".status-bar-timer");
381
+ if (timerEl) timerEl.textContent = elapsed + "s";
382
+ }, 100);
383
+ }
350
384
  } else {
385
+ // Not in-flight: show completion or remove
386
+ clearInterval(_statusBarTimerId);
387
+ _statusBarTimerId = null;
388
+
389
+ // Remove glow from input composer
351
390
  if (composer) composer.classList.remove("in-flight");
352
- _statusBarStartTime = 0;
353
- if (_statusBarTimerId) { clearInterval(_statusBarTimerId); _statusBarTimerId = null; }
391
+
392
+ if (existing && !existing.classList.contains("completed")) {
393
+ // Just finished — transition to completed state
394
+ var elapsed = _statusBarStartTime ? ((Date.now() - _statusBarStartTime) / 1000).toFixed(1) : "0.0";
395
+ existing.classList.add("completed");
396
+ existing.querySelector(".status-bar-label").textContent = "完成";
397
+ existing.querySelector(".status-bar-timer").textContent = elapsed + "s";
398
+ var dot = existing.querySelector(".status-bar-dot");
399
+ if (dot) dot.style.display = "none";
400
+ _statusBarStartTime = 0;
401
+ // Remove after animation ends
402
+ setTimeout(function() {
403
+ if (existing.parentNode) existing.remove();
404
+ }, 3000);
405
+ }
354
406
  }
355
407
  }
356
408
 
@@ -1373,12 +1425,10 @@
1373
1425
  '<div class="todo-progress-header" id="todo-progress-toggle">' +
1374
1426
  '<div class="todo-progress-left">' +
1375
1427
  '<span class="todo-progress-spinner"></span>' +
1376
- '<span class="todo-progress-message" id="todo-progress-message"></span>' +
1377
- '</div>' +
1378
- '<div class="todo-progress-right">' +
1379
- '<span class="todo-progress-status" id="todo-progress-status">运行中</span>' +
1380
- '<svg class="todo-progress-chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>' +
1428
+ '<span class="todo-progress-counter" id="todo-progress-counter">0/0</span>' +
1429
+ '<span class="todo-progress-task" id="todo-progress-task"></span>' +
1381
1430
  '</div>' +
1431
+ '<svg class="todo-progress-chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>' +
1382
1432
  '</div>' +
1383
1433
  '<div class="todo-progress-body hidden" id="todo-progress-body">' +
1384
1434
  '<ul class="todo-progress-list" id="todo-progress-list"></ul>' +
@@ -13746,6 +13796,17 @@
13746
13796
  }
13747
13797
 
13748
13798
  chatMessages.innerHTML = html;
13799
+ // 会话切换 / 首次渲染后,浏览器会把旧的 scrollTop 钳制到新内容
13800
+ // 的最大值——column-reverse 下这意味着视觉上跳到最上面(最旧消息),
13801
+ // 也就是用户反馈的"退出再回来时被重定向到最上面"。这里在
13802
+ // prevMsgCount === 0(resetChatRenderCache 后或刚从空状态进入)
13803
+ // 强制 scrollTop=0(视觉底部 = 最新消息),避免错位。
13804
+ // 注意:仅在"换会话/初次渲染"时强制重置;同一会话内的全量重渲染
13805
+ // (prevMsgCount > 0,比如 forceFullRender 或消息数减少)继续走
13806
+ // smartScrollToBottom,尊重用户的 chatAutoFollow 选择。
13807
+ if (prevMsgCount === 0) {
13808
+ chatMessages.scrollTop = 0;
13809
+ }
13749
13810
  attachAllCopyHandlers(chatMessages);
13750
13811
  bindChatScrollListener();
13751
13812
  applyPersistedExpandState(chatMessages);
@@ -13970,122 +14031,77 @@
13970
14031
  });
13971
14032
 
13972
14033
  function updateTodoProgress(messages) {
13973
- var container = document.getElementById("todo-progress");
13974
- if (!container) return;
13975
-
13976
- var session = state.sessions.find(function(s) { return s.id === state.selectedId; });
13977
- if (!session) {
13978
- container.classList.add("hidden");
13979
- return;
13980
- }
13981
-
13982
- // ── 左侧:用户最新发出的一条消息 ──
13983
- var lastUserText = "";
13984
- if (Array.isArray(messages)) {
13985
- for (var i = messages.length - 1; i >= 0; i--) {
13986
- var umsg = messages[i];
13987
- if (!umsg || umsg.role !== "user" || !Array.isArray(umsg.content)) continue;
13988
- for (var uj = 0; uj < umsg.content.length; uj++) {
13989
- var ublock = umsg.content[uj];
13990
- if (ublock && ublock.type === "text" && ublock.text && ublock.text.trim()) {
13991
- lastUserText = ublock.text.trim().replace(/\s+/g, " ");
13992
- break;
13993
- }
14034
+ var todos = null;
14035
+ // Scan all messages for latest TodoWrite tool_use
14036
+ for (var i = messages.length - 1; i >= 0; i--) {
14037
+ var msg = messages[i];
14038
+ if (!msg.content || !Array.isArray(msg.content)) continue;
14039
+ for (var j = msg.content.length - 1; j >= 0; j--) {
14040
+ var block = msg.content[j];
14041
+ if (block.type === "tool_use" && block.name === "TodoWrite" && block.input && block.input.todos) {
14042
+ todos = block.input.todos;
14043
+ break;
13994
14044
  }
13995
- if (lastUserText) break;
13996
14045
  }
14046
+ if (todos) break;
13997
14047
  }
13998
14048
 
13999
- // 没有任何用户消息(例如刚新建的空会话)才隐藏
14000
- if (!lastUserText) {
14049
+ var container = document.getElementById("todo-progress");
14050
+ if (!container) return;
14051
+
14052
+ if (!todos || todos.length === 0) {
14001
14053
  container.classList.add("hidden");
14002
14054
  return;
14003
14055
  }
14004
- container.classList.remove("hidden");
14005
14056
 
14006
- // ── 右侧:当前运行状态 ──
14007
- var sig = computeRunningSignal(session);
14008
- var isStructured = isStructuredSession(session);
14009
- var isRunning = isStructured ? !!sig.inFlight : !!sig.ptyRunning;
14010
- var statusText = isRunning
14011
- ? (isStructured ? "运行中" : "正在运行")
14012
- : "已完成";
14057
+ container.classList.remove("hidden");
14013
14058
 
14014
- // ── 步骤数:来自最近一次 TodoWrite ──
14015
- var todos = null;
14016
- if (Array.isArray(messages)) {
14017
- for (var ti = messages.length - 1; ti >= 0; ti--) {
14018
- var tmsg = messages[ti];
14019
- if (!tmsg.content || !Array.isArray(tmsg.content)) continue;
14020
- for (var tj = tmsg.content.length - 1; tj >= 0; tj--) {
14021
- var tblock = tmsg.content[tj];
14022
- if (tblock && tblock.type === "tool_use" && tblock.name === "TodoWrite" && tblock.input && tblock.input.todos) {
14023
- todos = tblock.input.todos;
14024
- break;
14025
- }
14059
+ var completed = 0;
14060
+ var inProgress = 0;
14061
+ var activeTask = "";
14062
+ for (var k = 0; k < todos.length; k++) {
14063
+ if (todos[k].status === "completed") completed++;
14064
+ if (todos[k].status === "in_progress") {
14065
+ inProgress++;
14066
+ if (!activeTask) {
14067
+ activeTask = todos[k].activeForm || todos[k].content || "";
14026
14068
  }
14027
- if (todos) break;
14028
14069
  }
14029
14070
  }
14030
14071
 
14031
- var hasTodos = !!(todos && todos.length > 0);
14032
- var allTodosDone = false;
14033
- var stepLabel = "";
14034
- if (hasTodos) {
14035
- var completedCount = 0;
14036
- var inProgressCount = 0;
14037
- for (var k = 0; k < todos.length; k++) {
14038
- if (todos[k].status === "completed") completedCount++;
14039
- if (todos[k].status === "in_progress") inProgressCount++;
14040
- }
14041
- allTodosDone = completedCount === todos.length;
14042
- if (isRunning && !allTodosDone) {
14043
- var stepIdx = Math.max(1, completedCount + (inProgressCount > 0 ? 1 : 0));
14044
- stepLabel = " · 第 " + stepIdx + "/" + todos.length + " 步";
14045
- }
14072
+ // 显示当前执行步骤 = 已完成 + 正在进行(如果有)
14073
+ var currentStep = completed + inProgress;
14074
+ var allDone = completed === todos.length;
14075
+ if (allDone) {
14076
+ // Hide todo when all tasks are completed
14077
+ container.classList.add("hidden");
14078
+ return;
14079
+ } else {
14080
+ container.classList.remove("all-done");
14046
14081
  }
14047
14082
 
14048
- // ── 应用状态类 ──
14049
- container.classList.toggle("is-running", isRunning);
14050
- // 复用已有 .all-done 样式(隐藏 spinner + 显示绿色 ✓)
14051
- container.classList.toggle("all-done", !isRunning);
14052
- container.classList.toggle("has-todos", hasTodos);
14053
-
14054
- // ── 写入 DOM ──
14055
- var msgEl = document.getElementById("todo-progress-message");
14056
- if (msgEl) msgEl.textContent = lastUserText;
14083
+ var counter = document.getElementById("todo-progress-counter");
14084
+ if (counter) counter.textContent = currentStep + "/" + todos.length;
14057
14085
 
14058
- var statusEl = document.getElementById("todo-progress-status");
14059
- if (statusEl) statusEl.textContent = statusText + stepLabel;
14086
+ var task = document.getElementById("todo-progress-task");
14087
+ if (task) task.textContent = activeTask;
14060
14088
 
14061
- // ── 渲染展开后的 todo 列表(仅当有 todos 时) ──
14089
+ // Render expanded list
14062
14090
  var list = document.getElementById("todo-progress-list");
14063
14091
  if (list) {
14064
- if (!hasTodos) {
14065
- list.innerHTML = "";
14066
- } else {
14067
- var html = "";
14068
- for (var m = 0; m < todos.length; m++) {
14069
- var t = todos[m];
14070
- var st = t.status || "pending";
14071
- var itemClass = st === "in_progress" ? "active" : st === "completed" ? "done" : "";
14072
- var iconClass = st === "in_progress" ? "active" : st === "completed" ? "done" : "pending";
14073
- var icon = st === "completed" ? "✓" : st === "in_progress" ? "›" : "○";
14074
- html += '<li class="todo-progress-item ' + itemClass + '">' +
14075
- '<span class="todo-item-icon ' + iconClass + '">' + icon + '</span>' +
14076
- '<span>' + escapeHtml(t.content || "") + '</span>' +
14077
- '</li>';
14078
- }
14079
- list.innerHTML = html;
14080
- }
14081
- }
14082
-
14083
- // 没有 todos 时强制收起 body(没东西可展开)
14084
- if (!hasTodos) {
14085
- todoExpanded = false;
14086
- container.classList.remove("expanded");
14087
- var body = document.getElementById("todo-progress-body");
14088
- if (body) body.classList.add("hidden");
14092
+ var html = "";
14093
+ for (var m = 0; m < todos.length; m++) {
14094
+ var t = todos[m];
14095
+ var st = t.status || "pending";
14096
+ var itemClass = st === "in_progress" ? "active" : st === "completed" ? "done" : "";
14097
+ var iconClass = st === "in_progress" ? "active" : st === "completed" ? "done" : "pending";
14098
+ var icon = st === "completed" ? "✓" : st === "in_progress" ? "›" : "○";
14099
+ html += '<li class="todo-progress-item ' + itemClass + '">' +
14100
+ '<span class="todo-item-icon ' + iconClass + '">' + icon + '</span>' +
14101
+ '<span>' + escapeHtml(t.content || "") + '</span>' +
14102
+ '</li>';
14103
+ }
14104
+ list.innerHTML = html;
14089
14105
  }
14090
14106
 
14091
14107
  // Sync todo progress to native notification
@@ -5390,38 +5390,6 @@
5390
5390
  text-overflow: ellipsis;
5391
5391
  white-space: nowrap;
5392
5392
  }
5393
- /* 左侧:用户最新发出的消息 */
5394
- .todo-progress-message {
5395
- flex: 1;
5396
- min-width: 0;
5397
- font-size: 0.75rem;
5398
- font-weight: 500;
5399
- color: var(--text-primary);
5400
- overflow: hidden;
5401
- text-overflow: ellipsis;
5402
- white-space: nowrap;
5403
- }
5404
- /* 右侧:状态文字 + 折叠箭头 */
5405
- .todo-progress-right {
5406
- display: flex;
5407
- align-items: center;
5408
- gap: 6px;
5409
- flex-shrink: 0;
5410
- min-width: 0;
5411
- }
5412
- .todo-progress-status {
5413
- font-size: 0.75rem;
5414
- font-weight: 500;
5415
- color: var(--text-secondary);
5416
- white-space: nowrap;
5417
- letter-spacing: 0.01em;
5418
- }
5419
- .todo-progress.is-running .todo-progress-status {
5420
- color: var(--accent);
5421
- }
5422
- .todo-progress.all-done .todo-progress-status {
5423
- color: #4a7a4f;
5424
- }
5425
5393
  .todo-progress-chevron {
5426
5394
  color: var(--text-muted);
5427
5395
  flex-shrink: 0;
@@ -5430,16 +5398,6 @@
5430
5398
  .todo-progress.expanded .todo-progress-chevron {
5431
5399
  transform: rotate(180deg);
5432
5400
  }
5433
- /* 没有 todo 列表时,没什么可展开的——隐藏箭头并去掉指针手势 */
5434
- .todo-progress:not(.has-todos) .todo-progress-chevron {
5435
- display: none;
5436
- }
5437
- .todo-progress:not(.has-todos) .todo-progress-header {
5438
- cursor: default;
5439
- }
5440
- .todo-progress:not(.has-todos) .todo-progress-header:hover {
5441
- background: transparent;
5442
- }
5443
5401
  .todo-progress-body {
5444
5402
  border-top: 1px solid var(--border-subtle);
5445
5403
  padding: 6px 10px 8px;
@@ -8580,9 +8538,6 @@
8580
8538
  .todo-progress-header { padding: 3px 6px; gap: 4px; }
8581
8539
  .todo-progress-counter { font-size: 0.5625rem; }
8582
8540
  .todo-progress-task { font-size: 0.5625rem; }
8583
- .todo-progress-message { font-size: 0.625rem; }
8584
- .todo-progress-status { font-size: 0.5625rem; }
8585
- .todo-progress-right { gap: 4px; }
8586
8541
  .todo-progress-chevron { width: 12px; height: 12px; }
8587
8542
  .todo-progress-body { padding: 4px 6px; }
8588
8543
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@co0ontty/wand",
3
- "version": "1.21.14",
3
+ "version": "1.21.16",
4
4
  "description": "A web terminal for local CLI tools like Claude.",
5
5
  "type": "module",
6
6
  "bin": {