@co0ontty/wand 1.33.0 → 1.34.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.
@@ -1390,7 +1390,7 @@
1390
1390
  checkDmgAutoUpdate();
1391
1391
  }
1392
1392
  if (state.claudeHistoryExpanded && !state.claudeHistoryLoaded) {
1393
- loadClaudeHistory();
1393
+ ensureClaudeHistoryLoaded();
1394
1394
  }
1395
1395
  });
1396
1396
  })
@@ -1462,8 +1462,11 @@
1462
1462
  // Suppress CSS transitions during initial DOM build
1463
1463
  document.documentElement.classList.add("no-transition");
1464
1464
 
1465
- // Apply persisted pin state before rendering
1466
- if (state.sidebarPinned && !isMobileLayout()) {
1465
+ // Apply persisted pin state before rendering.
1466
+ // 窄条(collapsed)形态不靠 .open 显示,靠 .pinned.collapsed 的 width:56px
1467
+ // 常驻;此时强制 sessionsDrawerOpen=true 会与 toggleSidebarCollapsed 里设的
1468
+ // false 打架,并在手机端误触发背景遮罩。窄条态下不强制 open。
1469
+ if (state.sidebarPinned && !state.sidebarCollapsed && !isMobileLayout()) {
1467
1470
  state.sessionsDrawerOpen = true;
1468
1471
  }
1469
1472
  app.innerHTML = isLoggedIn ? renderAppShell() : renderLogin();
@@ -3558,6 +3561,11 @@
3558
3561
 
3559
3562
  function toggleManageMode(force) {
3560
3563
  state.sessionsManageMode = typeof force === "boolean" ? force : !state.sessionsManageMode;
3564
+ if (state.sessionsManageMode && !state.claudeHistoryLoaded) {
3565
+ // 进入管理模式即后台补齐 Claude 历史,让「已选 N」「全选」计数从一开始
3566
+ // 就覆盖全部历史,而不是只统计已加载的那部分。
3567
+ ensureClaudeHistoryLoaded().then(updateSessionsList);
3568
+ }
3561
3569
  if (!state.sessionsManageMode) {
3562
3570
  clearManageSelections();
3563
3571
  closeSwipedItem();
@@ -3574,6 +3582,16 @@
3574
3582
  }
3575
3583
 
3576
3584
  function selectAllVisibleItems() {
3585
+ // 全选语义 = 选中所有可管理项(会话 + 全部 Claude 历史)。历史在登录后
3586
+ // 异步扫描,若用户在扫描完成前点「全选」,state.claudeHistory 仍为空会漏选,
3587
+ // 删除时表现为"只删了已加载的,跨目录/未扫完的历史还在"。这里先确保历史
3588
+ // 加载完成再全选。
3589
+ if (!state.claudeHistoryLoaded) {
3590
+ ensureClaudeHistoryLoaded().then(selectAllVisibleItems);
3591
+ return;
3592
+ }
3593
+ // 展开 Claude 历史分组,让用户能直观看到这些历史项也被选中了。
3594
+ state.claudeHistoryExpanded = true;
3577
3595
  var nextSessionIds = {};
3578
3596
  getSelectableSessions().forEach(function(session) {
3579
3597
  nextSessionIds[session.id] = true;
@@ -3782,6 +3800,21 @@
3782
3800
  });
3783
3801
  }
3784
3802
 
3803
+ // 去重包装:登录后历史会异步扫描,多个入口(管理模式、全选、展开分组)
3804
+ // 可能同时想确保历史就绪。共享同一个 in-flight Promise,避免重复 fetch,
3805
+ // 且在已加载时立即 resolve。
3806
+ var _claudeHistoryLoadingPromise = null;
3807
+ function ensureClaudeHistoryLoaded() {
3808
+ if (state.claudeHistoryLoaded) return Promise.resolve();
3809
+ if (_claudeHistoryLoadingPromise) return _claudeHistoryLoadingPromise;
3810
+ _claudeHistoryLoadingPromise = loadClaudeHistory().then(function() {
3811
+ _claudeHistoryLoadingPromise = null;
3812
+ }, function() {
3813
+ _claudeHistoryLoadingPromise = null;
3814
+ });
3815
+ return _claudeHistoryLoadingPromise;
3816
+ }
3817
+
3785
3818
  function isMobileLayout() {
3786
3819
  return window.innerWidth <= 768;
3787
3820
  }
@@ -5892,11 +5925,17 @@
5892
5925
  if (sidebarMoreBtn && sidebarOverflow) {
5893
5926
  sidebarMoreBtn.addEventListener("click", function(e) {
5894
5927
  e.stopPropagation();
5895
- sidebarOverflow.classList.toggle("open");
5928
+ var willOpen = !sidebarOverflow.classList.contains("open");
5929
+ sidebarOverflow.classList.toggle("open", willOpen);
5930
+ if (willOpen) positionSidebarOverflowMenu(sidebarOverflow);
5896
5931
  });
5897
5932
  document.addEventListener("click", function() {
5898
5933
  sidebarOverflow.classList.remove("open");
5899
5934
  });
5935
+ // 视口尺寸变化时关闭,避免 clamp 后的定位与新尺寸不符。
5936
+ window.addEventListener("resize", function() {
5937
+ sidebarOverflow.classList.remove("open");
5938
+ });
5900
5939
  }
5901
5940
  var homeBtn = document.getElementById("sidebar-home-btn");
5902
5941
  if (homeBtn) homeBtn.addEventListener("click", function() {
@@ -6964,7 +7003,7 @@
6964
7003
  event.stopPropagation();
6965
7004
  state.claudeHistoryExpanded = !state.claudeHistoryExpanded;
6966
7005
  if (state.claudeHistoryExpanded && !state.claudeHistoryLoaded) {
6967
- loadClaudeHistory();
7006
+ ensureClaudeHistoryLoaded();
6968
7007
  }
6969
7008
  updateSessionsList();
6970
7009
  return;
@@ -9588,10 +9627,55 @@
9588
9627
  // 桌面端展开窄条 → 300px 全栏常驻。
9589
9628
  state.sessionsDrawerOpen = true;
9590
9629
  }
9591
- render();
9630
+ // 轻量更新而非全量 render():render() 会 teardown 并重建整个终端 DOM,
9631
+ // 导致收窄/展开时终端闪烁、丢失滚动与渲染状态。这里只切布局 class
9632
+ // (宽度 56↔300 走 CSS width transition)、重渲侧栏列表内容、刷新
9633
+ // 收窄按钮自身的图标/文案,终端区保持不动。
9634
+ updateLayoutState();
9635
+ updateSessionsList();
9636
+ updateSidebarCollapseButton();
9637
+ hideCollapsedTileBubble();
9592
9638
  scheduleTerminalRefitAfterPaddingTransition();
9593
9639
  }
9594
9640
 
9641
+ // 收窄按钮的图标/title/状态随 collapsed 切换。抽出来给轻量更新路径用,
9642
+ // 避免为了换一个箭头方向就走全量 render()。
9643
+ function updateSidebarCollapseButton() {
9644
+ var btn = document.getElementById("sidebar-collapse-btn");
9645
+ if (!btn) return;
9646
+ var isCollapsed = !!state.sidebarPinned && !!state.sidebarCollapsed;
9647
+ btn.classList.toggle("collapsed", isCollapsed);
9648
+ btn.title = isCollapsed ? "展开侧栏" : "收起为窄条";
9649
+ btn.setAttribute("aria-label", isCollapsed ? "展开侧栏" : "收起为窄条");
9650
+ btn.innerHTML = isCollapsed
9651
+ ? '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="10 6 16 12 10 18"/><line x1="20" y1="5" x2="20" y2="19"/></svg>'
9652
+ : '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="14 6 8 12 14 18"/><line x1="4" y1="5" x2="4" y2="19"/></svg>';
9653
+ }
9654
+
9655
+ // 「更多操作」下拉默认 right:0 贴 more 按钮右沿向左展开。手机窄屏下这条会把
9656
+ // 菜单左缘顶出屏幕外。打开时按视口边界 clamp:先保持 CSS 默认右对齐,仅当
9657
+ // 真的越界才改写 left/right 把菜单拉回视口内(留 8px 边距)。
9658
+ function positionSidebarOverflowMenu(menu) {
9659
+ if (!menu) return;
9660
+ menu.style.left = "";
9661
+ menu.style.right = "";
9662
+ var parent = menu.offsetParent || menu.parentElement;
9663
+ if (!parent) return;
9664
+ var margin = 8;
9665
+ var parentRect = parent.getBoundingClientRect();
9666
+ var rect = menu.getBoundingClientRect();
9667
+ var vw = window.innerWidth;
9668
+ if (rect.left < margin) {
9669
+ // 左缘越界:改用 left 定位,把左缘顶到视口左 margin。
9670
+ menu.style.right = "auto";
9671
+ menu.style.left = (margin - parentRect.left) + "px";
9672
+ } else if (rect.right > vw - margin) {
9673
+ // 右缘越界:拉回视口右 margin(桌面右对齐时几乎不会触发)。
9674
+ menu.style.left = "auto";
9675
+ menu.style.right = (parentRect.right - (vw - margin)) + "px";
9676
+ }
9677
+ }
9678
+
9595
9679
 
9596
9680
  // Store last focused element for focus trap
9597
9681
  var lastFocusedElement = null;
@@ -574,9 +574,8 @@
574
574
  justify-content: center;
575
575
  padding: 0;
576
576
  box-shadow:
577
- 0 1px 3px rgba(184, 92, 55, 0.28),
578
- inset 0 1px 0 rgba(255, 255, 255, 0.28),
579
- inset 0 -1px 0 rgba(0, 0, 0, 0.08);
577
+ 0 1px 2px rgba(184, 92, 55, 0.22),
578
+ inset 0 1px 0 rgba(255, 255, 255, 0.28);
580
579
  transition:
581
580
  filter var(--transition-fast),
582
581
  box-shadow 0.22s ease,
@@ -586,40 +585,31 @@
586
585
  filter: brightness(1.06);
587
586
  transform: translateY(-1px);
588
587
  box-shadow:
589
- 0 4px 12px -2px rgba(184, 92, 55, 0.42),
590
- inset 0 1px 0 rgba(255, 255, 255, 0.35),
591
- inset 0 -1px 0 rgba(0, 0, 0, 0.08);
588
+ 0 4px 10px -3px rgba(184, 92, 55, 0.36),
589
+ inset 0 1px 0 rgba(255, 255, 255, 0.32);
592
590
  }
593
591
  /* History tile — softer cream tone to distinguish from active sessions */
594
592
  .sidebar-collapsed-tile.history {
595
593
  background: linear-gradient(145deg, #e8d3c0 0%, #d3b69d 50%, #b89478 100%);
596
594
  color: rgba(89, 58, 32, 0.88);
597
595
  box-shadow:
598
- 0 1px 3px rgba(120, 88, 56, 0.22),
599
- inset 0 1px 0 rgba(255, 255, 255, 0.45),
600
- inset 0 -1px 0 rgba(0, 0, 0, 0.06);
596
+ 0 1px 2px rgba(120, 88, 56, 0.18),
597
+ inset 0 1px 0 rgba(255, 255, 255, 0.42);
601
598
  }
602
599
  .sidebar-collapsed-tile.history:hover {
603
600
  box-shadow:
604
- 0 4px 12px -2px rgba(120, 88, 56, 0.34),
605
- inset 0 1px 0 rgba(255, 255, 255, 0.5),
606
- inset 0 -1px 0 rgba(0, 0, 0, 0.06);
601
+ 0 4px 10px -3px rgba(120, 88, 56, 0.3),
602
+ inset 0 1px 0 rgba(255, 255, 255, 0.48);
607
603
  }
608
- /* Active — liquid glass crown: outer ring + warm halo + crisp top sheen */
604
+ /* Active — accent ring + 一层柔和暖投影 + 顶高光。原先 7 层叠加在 36px 小块上
605
+ 糊成一团,精简到 3 层即可表达「升起 + 选中」。 */
609
606
  .sidebar-collapsed-tile.active {
610
607
  background: linear-gradient(145deg, #d27358 0%, #b35434 50%, #934128 100%);
611
608
  transform: translateY(-1px);
612
609
  box-shadow:
613
- /* outer glow halo */
614
- 0 0 0 1px rgba(255, 255, 255, 0.7),
615
- 0 0 0 3px rgba(197, 101, 61, 0.32),
616
- 0 8px 22px -4px rgba(160, 74, 46, 0.55),
617
- 0 3px 8px -2px rgba(160, 74, 46, 0.32),
618
- /* top sheen */
619
- inset 0 1px 0 rgba(255, 255, 255, 0.45),
620
- inset 1px 0 0 rgba(255, 255, 255, 0.18),
621
- /* bottom depth */
622
- inset 0 -1px 0 rgba(0, 0, 0, 0.16);
610
+ 0 0 0 2px rgba(197, 101, 61, 0.3),
611
+ 0 5px 14px -4px rgba(160, 74, 46, 0.45),
612
+ inset 0 1px 0 rgba(255, 255, 255, 0.4);
623
613
  }
624
614
  .sidebar-collapsed-tile:active {
625
615
  transform: translateY(0) scale(0.94);
@@ -9034,7 +9024,6 @@
9034
9024
  background: rgba(178, 79, 69, 0.12);
9035
9025
  color: var(--danger);
9036
9026
  transform: rotate(90deg);
9037
- box-shadow: 0 2px 8px rgba(178, 79, 69, 0.16);
9038
9027
  }
9039
9028
  .drawer-close-btn:active {
9040
9029
  background: rgba(178, 79, 69, 0.2);
@@ -9184,17 +9173,17 @@
9184
9173
  opacity: 1;
9185
9174
  }
9186
9175
 
9187
- /* ── Session item — clean card on glass ── */
9176
+ /* ── Session item — clean card on glass ──
9177
+ 克制原则:基础态用 hairline 描边 + 极轻顶部高光让卡片「贴」在侧栏上,
9178
+ 不给每张卡都加漂浮投影;hover/active 才升起一层柔和投影。避免多层
9179
+ box-shadow 在半透明侧栏里互相叠加显脏。 */
9188
9180
  .session-item {
9189
- background: linear-gradient(180deg, rgba(255, 255, 255, 0.62) 0%, rgba(255, 252, 247, 0.42) 100%);
9190
- border: 0.5px solid rgba(255, 255, 255, 0.55);
9181
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.55) 0%, rgba(255, 252, 247, 0.38) 100%);
9182
+ border: 1px solid rgba(125, 91, 57, 0.08);
9191
9183
  border-radius: 13px;
9192
9184
  padding: 11px 14px;
9193
9185
  margin-bottom: 6px;
9194
- box-shadow:
9195
- 0 0 0 0.5px rgba(125, 91, 57, 0.05),
9196
- 0 1px 2px rgba(125, 91, 57, 0.03),
9197
- inset 0 1px 0 rgba(255, 255, 255, 0.6);
9186
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5);
9198
9187
  transition:
9199
9188
  background 0.2s ease,
9200
9189
  border-color 0.2s ease,
@@ -9211,53 +9200,39 @@
9211
9200
  }
9212
9201
  .session-item:hover {
9213
9202
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.85) 0%, rgba(255, 253, 248, 0.62) 100%);
9214
- border-color: rgba(255, 255, 255, 0.7);
9203
+ border-color: rgba(125, 91, 57, 0.14);
9215
9204
  transform: translateY(-1px);
9216
- box-shadow:
9217
- 0 0 0 0.5px rgba(125, 91, 57, 0.08),
9218
- 0 6px 16px -6px rgba(125, 91, 57, 0.18),
9219
- inset 0 1px 0 rgba(255, 255, 255, 0.8);
9205
+ box-shadow: 0 4px 12px -4px rgba(125, 91, 57, 0.16);
9220
9206
  }
9221
9207
  .session-item:hover::before {
9222
9208
  opacity: 0.5;
9223
9209
  transform: scaleY(1);
9224
9210
  }
9225
- /* Selected (active) — true liquid glass pill with depth + accent glow */
9211
+ /* Selected (active) — accent ring 用边框表达,再叠一层柔和暖投影 + 顶高光。
9212
+ 去掉原先的 6 层叠加(外环/光晕/底影/三向 inset),层数过多在玻璃上显脏。 */
9226
9213
  .session-item.active {
9227
9214
  background:
9228
9215
  linear-gradient(180deg, rgba(255, 252, 247, 0.92) 0%, rgba(255, 244, 232, 0.78) 100%);
9229
- border: 0.5px solid rgba(255, 255, 255, 0.85);
9216
+ border: 1px solid rgba(197, 101, 61, 0.32);
9230
9217
  transform: translateY(-1px);
9231
9218
  backdrop-filter: blur(20px) saturate(180%);
9232
9219
  -webkit-backdrop-filter: blur(20px) saturate(180%);
9233
9220
  box-shadow:
9234
- /* outer accent ring (soft) */
9235
- 0 0 0 1px rgba(197, 101, 61, 0.28),
9236
- /* warm glow halo */
9237
- 0 8px 28px -6px rgba(197, 101, 61, 0.32),
9238
- /* base elevation */
9239
- 0 2px 6px -1px rgba(125, 91, 57, 0.1),
9240
- /* top inner highlight — light from above */
9241
- inset 0 1px 0 rgba(255, 255, 255, 0.95),
9242
- /* bottom inner shadow — depth at base */
9243
- inset 0 -1px 0 rgba(197, 101, 61, 0.08),
9244
- /* left inner glint */
9245
- inset 1px 0 0 rgba(255, 255, 255, 0.4);
9221
+ 0 4px 14px -4px rgba(197, 101, 61, 0.24),
9222
+ inset 0 1px 0 rgba(255, 255, 255, 0.7);
9246
9223
  }
9224
+ /* ::before 的左侧高亮条受父级 overflow:hidden 裁切,原来的 box-shadow 光晕
9225
+ 根本显示不出来,移除以省一次绘制。 */
9247
9226
  .session-item.active::before {
9248
9227
  opacity: 1;
9249
9228
  transform: scaleY(1);
9250
- box-shadow: 0 0 8px rgba(197, 101, 61, 0.5);
9251
9229
  }
9252
9230
  /* Multi-select state — quieter glass tint, no accent halo */
9253
9231
  .session-item.selected {
9254
9232
  background:
9255
9233
  linear-gradient(180deg, rgba(255, 250, 244, 0.85) 0%, rgba(255, 246, 236, 0.65) 100%);
9256
- border: 0.5px solid rgba(255, 255, 255, 0.7);
9257
- box-shadow:
9258
- 0 0 0 1px rgba(197, 101, 61, 0.18),
9259
- 0 4px 14px -4px rgba(125, 91, 57, 0.14),
9260
- inset 0 1px 0 rgba(255, 255, 255, 0.85);
9234
+ border: 1px solid rgba(197, 101, 61, 0.2);
9235
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6);
9261
9236
  }
9262
9237
  .session-item.selected::before {
9263
9238
  opacity: 0.7;
@@ -9402,21 +9377,19 @@
9402
9377
  transform: scale(0.96);
9403
9378
  transition-duration: 0.06s;
9404
9379
  }
9405
- /* Footer tab active — same liquid glass language as .session-item.active */
9380
+ /* Footer tab active — same liquid glass language as .session-item.active
9381
+ (同步精简为 accent 边框 + 1 层柔和投影 + 顶高光) */
9406
9382
  .sidebar-footer-actions .btn.active {
9407
9383
  background:
9408
9384
  linear-gradient(180deg, rgba(255, 252, 247, 0.95) 0%, rgba(255, 242, 228, 0.78) 100%);
9409
- border: 0.5px solid rgba(255, 255, 255, 0.85);
9385
+ border: 1px solid rgba(197, 101, 61, 0.28);
9410
9386
  color: var(--accent);
9411
9387
  transform: translateY(-1px);
9412
9388
  backdrop-filter: blur(16px) saturate(180%);
9413
9389
  -webkit-backdrop-filter: blur(16px) saturate(180%);
9414
9390
  box-shadow:
9415
- 0 0 0 1px rgba(197, 101, 61, 0.28),
9416
- 0 6px 18px -4px rgba(197, 101, 61, 0.32),
9417
- 0 2px 4px -1px rgba(125, 91, 57, 0.08),
9418
- inset 0 1px 0 rgba(255, 255, 255, 0.95),
9419
- inset 0 -1px 0 rgba(197, 101, 61, 0.08);
9391
+ 0 3px 10px -3px rgba(197, 101, 61, 0.22),
9392
+ inset 0 1px 0 rgba(255, 255, 255, 0.7);
9420
9393
  }
9421
9394
  .sidebar-footer-actions .btn.sidebar-logout:hover {
9422
9395
  background: rgba(178, 79, 69, 0.08);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@co0ontty/wand",
3
- "version": "1.33.0",
3
+ "version": "1.34.0",
4
4
  "description": "A web terminal for local CLI tools like Claude.",
5
5
  "type": "module",
6
6
  "bin": {