@co0ontty/wand 1.21.19 → 1.23.0

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.
@@ -1350,7 +1350,7 @@
1350
1350
  ) +
1351
1351
  '</div>' +
1352
1352
  '<div class="topbar-right">' +
1353
- (selectedSession && selectedSession.cwd ? '<button id="topbar-file-button" class="topbar-btn square' + (state.filePanelOpen ? ' active' : '') + '" type="button" aria-label="文件" title="文件"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg></button>' : '') +
1353
+ '<button id="topbar-file-button" class="topbar-btn square' + (state.filePanelOpen ? ' active' : '') + '" type="button" aria-label="文件" title="查看文件(可修改路径)"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg></button>' +
1354
1354
  '<span id="topbar-git-slot" class="topbar-git-slot">' + renderTopbarGitBadgeHtml() + '</span>' +
1355
1355
  (selectedSession ? renderTopbarMoreMenuHtml(selectedSession) : '') +
1356
1356
  '</div>' +
@@ -1366,7 +1366,7 @@
1366
1366
  '<div class="file-side-panel-body">' +
1367
1367
  '<div class="file-explorer-header">' +
1368
1368
  '<button class="file-explorer-up" id="file-explorer-up" type="button" title="返回上级目录" aria-label="返回上级目录">⬆</button>' +
1369
- '<span class="file-explorer-path" id="file-explorer-cwd" title="' + escapeHtml(selectedSession && selectedSession.cwd ? selectedSession.cwd : getConfigCwd()) + '">' + escapeHtml(selectedSession && selectedSession.cwd ? selectedSession.cwd : getConfigCwd()) + '</span>' +
1369
+ '<input type="text" class="file-explorer-path" id="file-explorer-cwd" value="' + escapeHtml(selectedSession && selectedSession.cwd ? selectedSession.cwd : getConfigCwd()) + '" title="' + escapeHtml(selectedSession && selectedSession.cwd ? selectedSession.cwd : getConfigCwd()) + '" placeholder="输入路径并回车..." spellcheck="false" autocomplete="off" autocapitalize="off" autocorrect="off" aria-label="当前路径,可直接修改后回车" />' +
1370
1370
  '<button class="file-explorer-toggle-hidden' + (state.fileExplorerShowHidden ? ' active' : '') + '" id="file-explorer-toggle-hidden" type="button" title="' + (state.fileExplorerShowHidden ? "隐藏点开头文件" : "显示隐藏文件") + '" aria-pressed="' + (state.fileExplorerShowHidden ? "true" : "false") + '">' + (state.fileExplorerShowHidden ? "👁" : "👁‍🗨") + '</button>' +
1371
1371
  '<button class="file-explorer-refresh" id="file-explorer-refresh" title="刷新" aria-label="刷新文件列表">↻</button>' +
1372
1372
  '</div>' +
@@ -1423,15 +1423,22 @@
1423
1423
  '<div class="composer-top-row">' +
1424
1424
  '<div id="todo-progress" class="todo-progress hidden">' +
1425
1425
  '<div class="todo-progress-header" id="todo-progress-toggle">' +
1426
- '<span class="todo-progress-spinner" aria-hidden="true"></span>' +
1427
- '<span class="todo-progress-message" id="todo-progress-message"></span>' +
1428
- '<span class="todo-progress-status" id="todo-progress-status">执行中</span>' +
1429
- '<svg class="todo-progress-chevron" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>' +
1430
- '</div>' +
1431
- '<div class="todo-progress-body hidden" id="todo-progress-body">' +
1432
- '<ul class="todo-progress-list" id="todo-progress-list"></ul>' +
1426
+ '<div class="todo-progress-left">' +
1427
+ '<span class="todo-progress-ring" id="todo-progress-ring" aria-hidden="true" style="--progress:0">' +
1428
+ '<svg width="16" height="16" viewBox="0 0 36 36">' +
1429
+ '<circle class="todo-ring-track" cx="18" cy="18" r="15.5" fill="none" stroke-width="4"/>' +
1430
+ '<circle class="todo-ring-fill" cx="18" cy="18" r="15.5" fill="none" stroke-width="4" stroke-linecap="round"/>' +
1431
+ '</svg>' +
1432
+ '</span>' +
1433
+ '<span class="todo-progress-counter" id="todo-progress-counter"></span>' +
1434
+ '<span class="todo-progress-task" id="todo-progress-task"></span>' +
1435
+ '</div>' +
1436
+ '<svg class="todo-progress-chevron" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 15 12 9 18 15"/></svg>' +
1433
1437
  '</div>' +
1434
1438
  '</div>' +
1439
+ '<div class="todo-progress-body hidden" id="todo-progress-body">' +
1440
+ '<ul class="todo-progress-list" id="todo-progress-list"></ul>' +
1441
+ '</div>' +
1435
1442
  '</div>' +
1436
1443
  '<div class="input-composer">' +
1437
1444
  '<button id="prompt-optimize-btn" class="prompt-optimize-btn" type="button" title="提示词优化(AI)" aria-label="提示词优化">' +
@@ -2877,7 +2884,15 @@
2877
2884
  var cwdEl = document.getElementById("file-explorer-cwd");
2878
2885
  if (!cwdEl) return;
2879
2886
  var cwd = session && session.cwd ? session.cwd : getConfigCwd();
2880
- cwdEl.textContent = cwd;
2887
+ // Don't clobber the user's in-progress edit when the input is focused.
2888
+ if (cwdEl.tagName === "INPUT") {
2889
+ if (document.activeElement !== cwdEl) {
2890
+ cwdEl.value = cwd;
2891
+ }
2892
+ } else {
2893
+ cwdEl.textContent = cwd;
2894
+ }
2895
+ cwdEl.title = cwd;
2881
2896
  }
2882
2897
 
2883
2898
  function closeFilePanel() {
@@ -3029,7 +3044,14 @@
3029
3044
  state.fileExplorerTotal = 0;
3030
3045
  explorer.innerHTML = '<div class="file-explorer"><div class="tree-loading" style="padding:12px;color:var(--text-muted);font-size:0.8125rem;">加载中…</div></div>';
3031
3046
  if (cwdEl) {
3032
- cwdEl.textContent = cwd;
3047
+ if (cwdEl.tagName === "INPUT") {
3048
+ // Avoid clobbering in-progress text while the user is typing.
3049
+ if (document.activeElement !== cwdEl) {
3050
+ cwdEl.value = cwd;
3051
+ }
3052
+ } else {
3053
+ cwdEl.textContent = cwd;
3054
+ }
3033
3055
  cwdEl.title = cwd;
3034
3056
  }
3035
3057
  var url = "/api/directory?q=" + encodeURIComponent(cwd) +
@@ -5084,6 +5106,56 @@
5084
5106
  var fileToggleHidden = document.getElementById("file-explorer-toggle-hidden");
5085
5107
  if (fileToggleHidden) fileToggleHidden.addEventListener("click", toggleExplorerHidden);
5086
5108
 
5109
+ // 路径输入框:支持点击修改路径,回车跳转,Esc 撤销。
5110
+ var fileCwdInput = document.getElementById("file-explorer-cwd");
5111
+ if (fileCwdInput && fileCwdInput.tagName === "INPUT") {
5112
+ var lastCommittedCwd = fileCwdInput.value;
5113
+ var normalizeCwdInput = function(raw) {
5114
+ var s = (raw || "").trim();
5115
+ if (!s) return "";
5116
+ // 折叠重复斜杠,去掉尾随斜杠(根目录除外)。
5117
+ s = s.replace(/\/{2,}/g, "/");
5118
+ if (s.length > 1) s = s.replace(/\/+$/, "");
5119
+ return s;
5120
+ };
5121
+ fileCwdInput.addEventListener("focus", function() {
5122
+ lastCommittedCwd = fileCwdInput.value;
5123
+ // Select all on focus so the user can immediately overwrite.
5124
+ setTimeout(function() {
5125
+ try { fileCwdInput.select(); } catch (e) {}
5126
+ }, 0);
5127
+ });
5128
+ fileCwdInput.addEventListener("keydown", function(e) {
5129
+ if (e.key === "Enter") {
5130
+ e.preventDefault();
5131
+ var next = normalizeCwdInput(fileCwdInput.value);
5132
+ if (!next) return;
5133
+ lastCommittedCwd = next;
5134
+ fileCwdInput.value = next;
5135
+ refreshFileExplorer({ cwd: next });
5136
+ fileCwdInput.blur();
5137
+ } else if (e.key === "Escape") {
5138
+ e.preventDefault();
5139
+ fileCwdInput.value = lastCommittedCwd;
5140
+ fileCwdInput.blur();
5141
+ }
5142
+ });
5143
+ fileCwdInput.addEventListener("blur", function() {
5144
+ var next = normalizeCwdInput(fileCwdInput.value);
5145
+ if (!next) {
5146
+ fileCwdInput.value = lastCommittedCwd;
5147
+ return;
5148
+ }
5149
+ if (next === lastCommittedCwd) {
5150
+ fileCwdInput.value = next;
5151
+ return;
5152
+ }
5153
+ lastCommittedCwd = next;
5154
+ fileCwdInput.value = next;
5155
+ refreshFileExplorer({ cwd: next });
5156
+ });
5157
+ }
5158
+
5087
5159
  // File search
5088
5160
  var fileSearchInput = document.getElementById("file-search-input");
5089
5161
  var fileSearchClear = document.getElementById("file-search-clear");
@@ -7523,6 +7595,14 @@
7523
7595
  // Reset todo progress bar
7524
7596
  var todoEl = document.getElementById("todo-progress");
7525
7597
  if (todoEl) todoEl.classList.add("hidden");
7598
+ // 同时清掉上一会话残留的 "回复中 N.Ns" 状态条以及它的计时器/glow。
7599
+ // 不清就会出现:切到新建的空会话,底部仍显示前一会话的 todolist + 回复中。
7600
+ var staleStatusBar = document.querySelector(".structured-status-bar");
7601
+ if (staleStatusBar) staleStatusBar.remove();
7602
+ if (_statusBarTimerId) { clearInterval(_statusBarTimerId); _statusBarTimerId = null; }
7603
+ _statusBarStartTime = 0;
7604
+ var staleComposer = document.querySelector(".input-composer");
7605
+ if (staleComposer) staleComposer.classList.remove("in-flight");
7526
7606
  var session = foundSession;
7527
7607
  state.preferredCommand = getPreferredTool();
7528
7608
  state.chatMode = getSafeModeForTool("claude", session && session.mode ? session.mode : state.chatMode);
@@ -13730,6 +13810,10 @@
13730
13810
  state.lastRenderedEmpty = "empty";
13731
13811
  state.lastRenderedMsgCount = 0;
13732
13812
  }
13813
+ // 空会话进入空状态前,把上一会话残留的状态条 / todo 进度条清掉。
13814
+ // 这里是 selectSession 之外的兜底:WS init 等异步路径也会落到这条空分支。
13815
+ renderStructuredStatusBar(null, selectedSession);
13816
+ updateTodoProgress([]);
13733
13817
  return;
13734
13818
  }
13735
13819
 
@@ -14067,10 +14151,10 @@
14067
14151
  if (prog && body) {
14068
14152
  if (todoExpanded) {
14069
14153
  prog.classList.add("expanded");
14070
- body.classList.remove("hidden");
14154
+ body.classList.add("expanded");
14071
14155
  } else {
14072
14156
  prog.classList.remove("expanded");
14073
- body.classList.add("hidden");
14157
+ body.classList.remove("expanded");
14074
14158
  }
14075
14159
  }
14076
14160
  });
@@ -14092,14 +14176,17 @@
14092
14176
  }
14093
14177
 
14094
14178
  var container = document.getElementById("todo-progress");
14179
+ var bodyEl = document.getElementById("todo-progress-body");
14095
14180
  if (!container) return;
14096
14181
 
14097
14182
  if (!todos || todos.length === 0) {
14098
14183
  container.classList.add("hidden");
14184
+ if (bodyEl) bodyEl.classList.add("hidden");
14099
14185
  return;
14100
14186
  }
14101
14187
 
14102
14188
  container.classList.remove("hidden");
14189
+ if (bodyEl) bodyEl.classList.remove("hidden");
14103
14190
 
14104
14191
  var completed = 0;
14105
14192
  var inProgress = 0;
@@ -14121,6 +14208,7 @@
14121
14208
  if (allDone) {
14122
14209
  // Hide todo when all tasks are completed
14123
14210
  container.classList.add("hidden");
14211
+ if (bodyEl) bodyEl.classList.add("hidden");
14124
14212
  return;
14125
14213
  } else {
14126
14214
  container.classList.remove("all-done");
@@ -14132,6 +14220,14 @@
14132
14220
  var task = document.getElementById("todo-progress-task");
14133
14221
  if (task) task.textContent = activeTask;
14134
14222
 
14223
+ // Drive the circular progress ring with the honest "completed / total" fraction
14224
+ // (counter text shows the 1-indexed current step, ring shows actual done ratio).
14225
+ var ring = document.getElementById("todo-progress-ring");
14226
+ if (ring) {
14227
+ var ratio = todos.length > 0 ? completed / todos.length : 0;
14228
+ ring.style.setProperty("--progress", ratio.toFixed(3));
14229
+ }
14230
+
14135
14231
  // Render expanded list
14136
14232
  var list = document.getElementById("todo-progress-list");
14137
14233
  if (list) {
@@ -685,6 +685,40 @@
685
685
  font-weight: 500;
686
686
  }
687
687
 
688
+ /* When rendered as <input> (editable path) keep the same look but reveal
689
+ an editable affordance on hover/focus. */
690
+ input.file-explorer-path {
691
+ background: transparent;
692
+ border: 1px solid transparent;
693
+ border-radius: var(--radius-sm);
694
+ padding: 4px 8px;
695
+ height: 28px;
696
+ line-height: 1.2;
697
+ cursor: text;
698
+ transition: background-color var(--transition-fast),
699
+ border-color var(--transition-fast),
700
+ color var(--transition-fast);
701
+ outline: none;
702
+ text-overflow: ellipsis;
703
+ width: 100%;
704
+ box-sizing: border-box;
705
+ }
706
+ input.file-explorer-path::placeholder {
707
+ color: var(--text-muted);
708
+ opacity: 0.6;
709
+ }
710
+ input.file-explorer-path:hover {
711
+ background: rgba(255, 255, 255, 0.5);
712
+ border-color: var(--border);
713
+ color: var(--text-secondary);
714
+ }
715
+ input.file-explorer-path:focus {
716
+ background: var(--bg-primary);
717
+ border-color: var(--accent);
718
+ color: var(--text-primary);
719
+ text-overflow: clip;
720
+ }
721
+
688
722
  .file-explorer-actions { display: flex; gap: 4px; margin-left: auto; flex-shrink: 0; }
689
723
 
690
724
  .file-explorer-refresh {
@@ -5311,10 +5345,12 @@
5311
5345
  .input-label { font-size: 0.6875rem; color: var(--text-muted); font-weight: 500; }
5312
5346
  .input-textarea-wrap { position: relative; width: 100%; }
5313
5347
 
5314
- /* Composer top row: holds todo collapse bar (left) + reply status bar (right) on one line */
5348
+ /* Composer top row: holds todo collapse bar (left) + reply status bar (right) on one line.
5349
+ position: relative anchors the upward-floating .todo-progress-body to the full row width. */
5315
5350
  .composer-top-row {
5351
+ position: relative;
5316
5352
  display: flex;
5317
- align-items: flex-start;
5353
+ align-items: center;
5318
5354
  gap: 8px;
5319
5355
  min-width: 0;
5320
5356
  }
@@ -5357,31 +5393,52 @@
5357
5393
  min-width: 0;
5358
5394
  flex: 1;
5359
5395
  }
5360
- .todo-progress-spinner {
5361
- width: 14px;
5362
- height: 14px;
5363
- min-width: 14px;
5364
- border: 2px solid var(--accent-muted);
5365
- border-top-color: var(--accent);
5366
- border-radius: 50%;
5367
- animation: spin 0.8s linear infinite;
5396
+ /* Circular progress ring (replaces the old plain spinner) */
5397
+ .todo-progress-ring {
5398
+ position: relative;
5399
+ display: inline-flex;
5400
+ align-items: center;
5401
+ justify-content: center;
5402
+ width: 16px;
5403
+ height: 16px;
5404
+ min-width: 16px;
5405
+ flex-shrink: 0;
5368
5406
  }
5369
- .todo-progress.all-done .todo-progress-spinner {
5370
- display: none;
5407
+ .todo-progress-ring svg {
5408
+ width: 100%;
5409
+ height: 100%;
5410
+ transform: rotate(-90deg);
5371
5411
  }
5372
- .todo-progress.all-done .todo-progress-left::before {
5373
- content: "✓";
5374
- font-size: 0.8125rem;
5375
- font-weight: 700;
5376
- color: #4a7a4f;
5377
- width: 14px;
5378
- text-align: center;
5412
+ .todo-ring-track {
5413
+ stroke: rgba(197, 101, 61, 0.18);
5414
+ }
5415
+ .todo-ring-fill {
5416
+ stroke: var(--accent);
5417
+ /* circumference = 2 * π * 15.5 ≈ 97.39 */
5418
+ stroke-dasharray: 97.39;
5419
+ stroke-dashoffset: calc(97.39 * (1 - var(--progress, 0)));
5420
+ transition: stroke-dashoffset 0.35s ease;
5421
+ }
5422
+ .todo-progress-ring::after {
5423
+ content: "";
5424
+ position: absolute;
5425
+ width: 4px;
5426
+ height: 4px;
5427
+ border-radius: 50%;
5428
+ background: var(--accent);
5429
+ opacity: 0.6;
5430
+ animation: ringPulse 1.4s ease-in-out infinite;
5431
+ }
5432
+ @keyframes ringPulse {
5433
+ 0%, 100% { opacity: 0.35; transform: scale(0.85); }
5434
+ 50% { opacity: 0.95; transform: scale(1.05); }
5379
5435
  }
5380
5436
  .todo-progress-counter {
5381
5437
  font-size: 0.75rem;
5382
5438
  font-weight: 600;
5383
5439
  color: var(--text-primary);
5384
5440
  white-space: nowrap;
5441
+ font-variant-numeric: tabular-nums;
5385
5442
  }
5386
5443
  .todo-progress-task {
5387
5444
  font-size: 0.75rem;
@@ -5398,44 +5455,84 @@
5398
5455
  .todo-progress.expanded .todo-progress-chevron {
5399
5456
  transform: rotate(180deg);
5400
5457
  }
5458
+
5459
+ /* Expanded body floats UPWARD above the composer, spans the full row width,
5460
+ and is decoupled from the flex row so the status bar on the right does not
5461
+ animate together with the expansion. */
5401
5462
  .todo-progress-body {
5402
- border-top: 1px solid var(--border-subtle);
5403
- padding: 6px 10px 8px;
5463
+ position: absolute;
5464
+ left: 0;
5465
+ right: 0;
5466
+ bottom: calc(100% + 6px);
5467
+ background: rgba(255, 251, 246, 0.98);
5468
+ border: 1px solid var(--border-default);
5469
+ border-radius: 12px;
5470
+ padding: 8px 10px;
5471
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08), 0 2px 6px rgba(0, 0, 0, 0.04);
5472
+ backdrop-filter: blur(8px);
5473
+ -webkit-backdrop-filter: blur(8px);
5474
+ max-height: 320px;
5475
+ overflow-y: auto;
5476
+ z-index: 30;
5477
+ opacity: 0;
5478
+ transform: translateY(6px);
5479
+ pointer-events: none;
5480
+ transition: opacity 0.18s ease, transform 0.18s ease;
5404
5481
  }
5405
5482
  .todo-progress-body.hidden { display: none; }
5483
+ .todo-progress-body.expanded {
5484
+ opacity: 1;
5485
+ transform: translateY(0);
5486
+ pointer-events: auto;
5487
+ }
5406
5488
  .todo-progress-list {
5407
5489
  list-style: none;
5408
5490
  margin: 0;
5409
5491
  padding: 0;
5410
5492
  display: flex;
5411
5493
  flex-direction: column;
5412
- gap: 3px;
5494
+ gap: 1px;
5413
5495
  }
5414
5496
  .todo-progress-item {
5415
5497
  display: flex;
5416
5498
  align-items: center;
5417
- gap: 8px;
5418
- padding: 3px 4px;
5419
- border-radius: 6px;
5420
- font-size: 0.75rem;
5499
+ gap: 10px;
5500
+ padding: 6px 8px;
5501
+ border-radius: 8px;
5502
+ font-size: 0.8125rem;
5421
5503
  color: var(--text-secondary);
5504
+ line-height: 1.35;
5422
5505
  transition: background var(--transition-fast);
5423
5506
  }
5507
+ .todo-progress-item + .todo-progress-item {
5508
+ border-top: 1px solid var(--border-subtle);
5509
+ border-radius: 0;
5510
+ }
5511
+ .todo-progress-item:first-child { border-top-left-radius: 8px; border-top-right-radius: 8px; }
5512
+ .todo-progress-item:last-child { border-bottom-left-radius: 8px; border-bottom-right-radius: 8px; }
5424
5513
  .todo-progress-item.active {
5425
5514
  color: var(--text-primary);
5426
5515
  font-weight: 500;
5427
- background: rgba(197, 101, 61, 0.06);
5516
+ background: rgba(197, 101, 61, 0.07);
5428
5517
  }
5429
5518
  .todo-progress-item.done {
5430
5519
  color: var(--text-muted);
5431
5520
  text-decoration: line-through;
5432
5521
  text-decoration-color: var(--text-muted);
5433
5522
  }
5523
+ .todo-progress-item > span:last-child {
5524
+ flex: 1;
5525
+ min-width: 0;
5526
+ overflow: hidden;
5527
+ text-overflow: ellipsis;
5528
+ white-space: nowrap;
5529
+ }
5434
5530
  .todo-item-icon {
5435
5531
  width: 16px;
5436
5532
  min-width: 16px;
5437
5533
  text-align: center;
5438
- font-size: 0.6875rem;
5534
+ font-size: 0.75rem;
5535
+ flex-shrink: 0;
5439
5536
  }
5440
5537
  .todo-item-icon.pending { color: var(--text-muted); }
5441
5538
  .todo-item-icon.active { color: var(--accent); }
@@ -10473,7 +10570,10 @@
10473
10570
  }
10474
10571
 
10475
10572
  .file-preview-modal {
10476
- width: min(90vw, calc(100vw - 380px));
10573
+ /* 桌面端:留出侧边栏空间,但保证最小宽度,避免在窄视口上塌成一条缝。
10574
+ 之前是 min(90vw, calc(100vw - 380px)),当 100vw < 380px 时整个值为负,
10575
+ 元素直接被收成 0 宽。这里用 max() 兜底一个最小宽度。 */
10576
+ width: min(90vw, max(640px, calc(100vw - 380px)));
10477
10577
  max-width: 1000px;
10478
10578
  height: 80vh;
10479
10579
  max-height: 85vh;
@@ -10492,6 +10592,65 @@
10492
10592
  to { opacity: 1; transform: translateY(0) scale(1); }
10493
10593
  }
10494
10594
 
10595
+ /* 移动端(含竖屏手机):铺满整个视口,去掉圆角和边框,
10596
+ 使用 dvh 兼容浏览器地址栏占用。 */
10597
+ @media (max-width: 768px) {
10598
+ .file-preview-overlay {
10599
+ padding: 0;
10600
+ align-items: stretch;
10601
+ justify-content: stretch;
10602
+ }
10603
+ .file-preview-modal {
10604
+ width: 100vw;
10605
+ max-width: 100vw;
10606
+ height: 100vh;
10607
+ height: 100dvh;
10608
+ max-height: 100vh;
10609
+ max-height: 100dvh;
10610
+ border-radius: 0;
10611
+ border: none;
10612
+ box-shadow: none;
10613
+ animation: fade-in 0.18s ease;
10614
+ }
10615
+ .file-preview-header {
10616
+ padding: 10px 12px;
10617
+ gap: 8px;
10618
+ /* 给安全区让位,例如带刘海的设备 */
10619
+ padding-top: max(10px, env(safe-area-inset-top));
10620
+ padding-left: max(12px, env(safe-area-inset-left));
10621
+ padding-right: max(12px, env(safe-area-inset-right));
10622
+ }
10623
+ .file-preview-title {
10624
+ font-size: 0.875rem;
10625
+ gap: 6px;
10626
+ min-width: 0;
10627
+ flex: 1;
10628
+ }
10629
+ .file-preview-filename {
10630
+ max-width: 100%;
10631
+ }
10632
+ /* 路径在窄屏上极易抢空间,直接隐藏,hover/长按文件名仍可看到完整路径。 */
10633
+ .file-preview-path {
10634
+ display: none;
10635
+ }
10636
+ .file-preview-toolbar {
10637
+ gap: 4px;
10638
+ flex-shrink: 0;
10639
+ }
10640
+ .file-preview-toolbar-btn {
10641
+ padding: 4px 6px;
10642
+ font-size: 0.75rem;
10643
+ }
10644
+ .file-preview-close {
10645
+ font-size: 1.375rem;
10646
+ padding: 6px 10px;
10647
+ }
10648
+ /* 底部安全区(小白条 / 圆角) */
10649
+ .file-preview-body {
10650
+ padding-bottom: env(safe-area-inset-bottom);
10651
+ }
10652
+ }
10653
+
10495
10654
  .file-preview-header {
10496
10655
  display: flex;
10497
10656
  align-items: center;
@@ -11919,7 +12078,9 @@
11919
12078
  100% { background-position: 100% center; }
11920
12079
  }
11921
12080
 
11922
- /* ── 结构化会话状态条(与 todo 折叠栏共处一行,靠右) ── */
12081
+ /* ── 结构化会话状态条(与 todo 折叠栏共处一行,靠右) ──
12082
+ Only opacity/color transition — `transition: all` would cause this bar to
12083
+ animate together with the todo bar's expansion, which looks broken. */
11923
12084
  .structured-status-bar {
11924
12085
  display: flex;
11925
12086
  align-items: center;
@@ -11932,7 +12093,7 @@
11932
12093
  border: none;
11933
12094
  font-size: 0.6875rem;
11934
12095
  color: var(--text-muted);
11935
- transition: all 0.3s ease;
12096
+ transition: opacity 0.3s ease, color 0.3s ease;
11936
12097
  overflow: hidden;
11937
12098
  flex-shrink: 0;
11938
12099
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@co0ontty/wand",
3
- "version": "1.21.19",
3
+ "version": "1.23.0",
4
4
  "description": "A web terminal for local CLI tools like Claude.",
5
5
  "type": "module",
6
6
  "bin": {