@co0ontty/wand 1.43.0 → 1.43.2

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.
@@ -1,6 +1,6 @@
1
1
  {
2
- "commit": "a2fcf61e1ef6825cb1767b63fb9593e8f6e4ecea",
3
- "builtAt": "2026-05-31T02:16:53.049Z",
4
- "version": "1.43.0",
2
+ "commit": "968ab29608433dbadc5cd58b11323ba51f757d2d",
3
+ "builtAt": "2026-05-31T05:44:17.877Z",
4
+ "version": "1.43.2",
5
5
  "channel": "stable"
6
6
  }
@@ -1563,7 +1563,7 @@
1563
1563
 
1564
1564
  // ===== 桌面:点 sidebar 外的空白处自动收起 =====
1565
1565
  // 只对「临时打开但未锁定」的全尺寸侧栏生效;已锁定的 pinned 侧栏
1566
- // 必须保持常驻,除非用户明确点 X / hamburger 关闭。
1566
+ // 必须保持常驻,除非用户明确点 X 关闭。
1567
1567
  // - 仅 desktop + 未锁定 + 全尺寸(非窄条)+ 已打开 时生效
1568
1568
  // - 窄条态不触发(窄条本来就是稳定常驻形态)
1569
1569
  // - 手机端由 .drawer-backdrop 元素自己接住点击,不在这里重复处理
@@ -1588,7 +1588,7 @@
1588
1588
  ".folder-picker-dropdown, .path-suggestions, " +
1589
1589
  ".permission-prompt-overlay, .restart-overlay"
1590
1590
  )) return;
1591
- closeSessionsDrawer();
1591
+ closeTransientSessionsDrawer();
1592
1592
  }, true);
1593
1593
 
1594
1594
  renderBootLoading();
@@ -1783,6 +1783,7 @@
1783
1783
  var terminalInfo = selectedSession ? (selectedSession.mode + " | " + selectedSession.status) : "点击上方「新对话」开始";
1784
1784
  var currentDraft = state.selectedId ? (state.drafts[state.selectedId] || "") : "";
1785
1785
  var drawerClass = state.sessionsDrawerOpen ? " open" : "";
1786
+ var backdropClass = shouldShowSessionsBackdrop() ? " open" : "";
1786
1787
  var preferredTool = getComposerTool();
1787
1788
  var composerMode = getSafeModeForTool(preferredTool, state.chatMode);
1788
1789
 
@@ -1796,7 +1797,7 @@
1796
1797
  var collapsedCls = isCollapsed ? ' sidebar-collapsed' : '';
1797
1798
  var sidebarCollapsedCls = isCollapsed ? ' collapsed' : '';
1798
1799
  return '<div class="app-container">' +
1799
- '<div id="sessions-drawer-backdrop" class="drawer-backdrop' + drawerClass + '"></div>' +
1800
+ '<div id="sessions-drawer-backdrop" class="drawer-backdrop' + backdropClass + '"></div>' +
1800
1801
  '<div class="main-layout' + (state.sessionsDrawerOpen ? ' sidebar-open' : '') + (isAnchored ? ' sidebar-pinned' : '') + collapsedCls + '">' +
1801
1802
  '<aside id="sessions-drawer" class="sidebar' + drawerClass + (isAnchored ? ' pinned' : '') + sidebarCollapsedCls + '">' +
1802
1803
  '<div class="sidebar-header">' +
@@ -2245,7 +2246,11 @@
2245
2246
  var quickCommitDragState = null;
2246
2247
 
2247
2248
  function normalizeQuickCommitAction(value) {
2248
- if (value === "commit-tag" || value === "commit-tag-push") return value;
2249
+ if (
2250
+ value === "commit-tag" ||
2251
+ value === "commit-tag-push" ||
2252
+ value === "commit-push"
2253
+ ) return value;
2249
2254
  return "commit";
2250
2255
  }
2251
2256
 
@@ -2258,7 +2263,7 @@
2258
2263
  verb: "提交、打 Tag 并推送",
2259
2264
  withTag: true,
2260
2265
  push: true,
2261
- tone: "push",
2266
+ tone: "all",
2262
2267
  };
2263
2268
  }
2264
2269
  if (action === "commit-tag") {
@@ -2271,6 +2276,16 @@
2271
2276
  tone: "tag",
2272
2277
  };
2273
2278
  }
2279
+ if (action === "commit-push") {
2280
+ return {
2281
+ action: action,
2282
+ label: "Commit + Push",
2283
+ verb: "提交并推送",
2284
+ withTag: false,
2285
+ push: true,
2286
+ tone: "push",
2287
+ };
2288
+ }
2274
2289
  return {
2275
2290
  action: "commit",
2276
2291
  label: "Commit",
@@ -2281,16 +2296,11 @@
2281
2296
  };
2282
2297
  }
2283
2298
 
2284
- function getQuickCommitActionFromRatio(ratio) {
2285
- if (ratio >= 0.74) return "commit-tag-push";
2286
- if (ratio >= 0.38) return "commit-tag";
2287
- return "commit";
2288
- }
2289
-
2290
2299
  function openQuickCommitModal() {
2291
2300
  if (!state.selectedId) return;
2292
2301
  state.quickCommitOpen = true;
2293
2302
  state.quickCommitSubmitting = false;
2303
+ state.quickCommitAutoGenerating = false;
2294
2304
  state.quickCommitError = "";
2295
2305
  state.quickCommitForm = {
2296
2306
  customMessage: "",
@@ -2416,121 +2426,194 @@
2416
2426
  attachQuickCommitDrag();
2417
2427
  }
2418
2428
 
2419
- function updateQuickCommitDragVisual(action, ratio) {
2420
- action = normalizeQuickCommitAction(action);
2421
- state.quickCommitDragAction = action;
2422
- var track = document.getElementById("quick-commit-drag-track");
2423
- if (!track) return;
2424
- var meta = getQuickCommitActionMeta(action);
2425
- var progress = typeof ratio === "number"
2426
- ? Math.max(0, Math.min(1, ratio))
2427
- : (action === "commit-tag-push" ? 1 : (action === "commit-tag" ? 0.5 : 0));
2428
- track.setAttribute("data-action", action);
2429
- track.style.setProperty("--qc-progress", (progress * 100).toFixed(1) + "%");
2430
- var knob = document.getElementById("quick-commit-drag-action");
2431
- if (knob) {
2432
- var maxX = Math.max(0, track.clientWidth - knob.offsetWidth - 14);
2433
- track.style.setProperty("--qc-knob-x", (maxX * progress).toFixed(1) + "px");
2434
- }
2435
- var label = document.getElementById("quick-commit-drag-label");
2436
- if (label) label.textContent = state.quickCommitSubmitting ? "执行中..." : meta.label;
2437
- var stages = track.querySelectorAll("[data-qc-stage]");
2438
- var order = { "commit": 0, "commit-tag": 1, "commit-tag-push": 2 };
2439
- for (var i = 0; i < stages.length; i++) {
2440
- var stageAction = stages[i].getAttribute("data-qc-stage");
2441
- var passed = order[stageAction] <= order[action];
2442
- stages[i].classList.toggle("is-active", stageAction === action);
2443
- stages[i].classList.toggle("is-passed", passed);
2444
- }
2429
+ // Compose the final action from which stations the orb attached during the drag.
2430
+ // Commit is implicit — every action starts with a commit.
2431
+ function composeOrbAction(attached) {
2432
+ var hasTag = !!(attached && attached.tag);
2433
+ var hasPush = !!(attached && attached.push);
2434
+ if (hasTag && hasPush) return "commit-tag-push";
2435
+ if (hasTag) return "commit-tag";
2436
+ if (hasPush) return "commit-push";
2437
+ return "commit";
2438
+ }
2439
+
2440
+ // How close (px) the pointer must come to a loose chip's home before that chip is
2441
+ // magnetically picked up into the chip currently being dragged.
2442
+ var QC_DOCK_PICKUP_R = 58;
2443
+
2444
+ // A plain tap on a chip fires its own action directly (tag/push imply a commit too).
2445
+ function qcChipTapAction(id) {
2446
+ if (id === "tag") return "commit-tag";
2447
+ if (id === "push") return "commit-push";
2448
+ return "commit";
2445
2449
  }
2446
2450
 
2451
+ // Magnetic dock: three loose chips (Commit / Tag / Push) rest in a field. Grab ANY chip and
2452
+ // drag it; whenever the pointer brushes another chip it sticks to the travelling cluster and
2453
+ // moves along (ordered Commit → Tag → Push). Fling the cluster into the right-side ▶ pad to
2454
+ // fire compose(members) — a commit is always implied. Release anywhere else and every chip
2455
+ // springs back home. A plain tap (no drag) on a chip fires that chip's own action.
2447
2456
  function attachQuickCommitDrag() {
2448
- var track = document.getElementById("quick-commit-drag-track");
2449
- var knob = document.getElementById("quick-commit-drag-action");
2450
- if (!track || !knob) return;
2451
- updateQuickCommitDragVisual(state.quickCommitDragAction || "commit");
2457
+ var field = document.getElementById("qc-dock-field");
2458
+ var stage = document.getElementById("qc-dock-stage");
2459
+ var launch = document.getElementById("qc-dock-launch");
2460
+ var cluster = document.getElementById("qc-dock-cluster");
2461
+ if (!field || !stage || !launch || !cluster) return;
2462
+
2463
+ var ORDER = ["commit", "tag", "push"];
2464
+ var chips = {};
2465
+ ORDER.forEach(function(id) { chips[id] = field.querySelector('[data-chip="' + id + '"]'); });
2466
+ if (!chips.commit || !chips.tag || !chips.push) return;
2467
+
2468
+ function cw(id) { return chips[id] ? chips[id].offsetWidth : 90; }
2469
+ function chH() { return chips.commit ? chips.commit.offsetHeight : 38; }
2470
+
2471
+ // Resting (home) positions — the three chips in one centered row.
2472
+ function homePositions() {
2473
+ var fw = field.clientWidth, fh = field.clientHeight, H = chH();
2474
+ var gap = 14;
2475
+ var total = ORDER.reduce(function(s, id) { return s + cw(id); }, 0) + (ORDER.length - 1) * gap;
2476
+ var x = Math.max(8, (fw - total) / 2);
2477
+ var y = (fh - H) / 2;
2478
+ var pos = {};
2479
+ ORDER.forEach(function(id) { pos[id] = { x: x, y: y }; x += cw(id) + gap; });
2480
+ return pos;
2481
+ }
2482
+
2483
+ var home = {};
2484
+ function placeChip(id, x, y) {
2485
+ if (chips[id]) chips[id].style.transform = "translate(" + x.toFixed(1) + "px," + y.toFixed(1) + "px)";
2486
+ }
2487
+ function layoutHome(animated) {
2488
+ home = homePositions();
2489
+ ORDER.forEach(function(id) {
2490
+ chips[id].classList.toggle("qc-chip--anim", !!animated);
2491
+ placeChip(id, home[id].x, home[id].y);
2492
+ });
2493
+ }
2494
+ layoutHome(false);
2495
+
2496
+ var drag = null;
2497
+
2498
+ // Lay the current cluster members in a tight row centered on (cx, cy) within the field.
2499
+ function layoutCluster(members, cx, cy) {
2500
+ var H = chH(), gapIn = 5;
2501
+ var ids = ORDER.filter(function(id) { return members.indexOf(id) >= 0; });
2502
+ var total = ids.reduce(function(s, id) { return s + cw(id); }, 0) + Math.max(0, ids.length - 1) * gapIn;
2503
+ var fh = field.clientHeight;
2504
+ var x = cx - total / 2;
2505
+ var y = Math.max(2, Math.min(fh - H - 2, cy - H / 2));
2506
+ ids.forEach(function(id) { placeChip(id, x, y); x += cw(id) + gapIn; });
2507
+ return { x: cx - total / 2 - 7, y: y - 7, w: total + 14, h: H + 14 };
2508
+ }
2509
+ function showCluster(box) {
2510
+ cluster.classList.add("is-active");
2511
+ cluster.style.transform = "translate(" + box.x.toFixed(1) + "px," + box.y.toFixed(1) + "px)";
2512
+ cluster.style.width = box.w.toFixed(1) + "px";
2513
+ cluster.style.height = box.h.toFixed(1) + "px";
2514
+ }
2515
+ function hideCluster() { cluster.classList.remove("is-active"); }
2516
+
2517
+ function clusterAction(members) {
2518
+ return composeOrbAction({
2519
+ commit: true,
2520
+ tag: members.indexOf("tag") >= 0,
2521
+ push: members.indexOf("push") >= 0,
2522
+ });
2523
+ }
2524
+ function setLaunchLabel(t) {
2525
+ var l = document.getElementById("qc-dock-launch-label");
2526
+ if (l) l.textContent = t;
2527
+ }
2528
+ function pointInLaunch(x, y) {
2529
+ var r = launch.getBoundingClientRect();
2530
+ return x >= r.left && x <= r.right && y >= r.top && y <= r.bottom;
2531
+ }
2452
2532
 
2453
- var onPointerDown = function(e) {
2454
- if (knob.disabled || isQuickCommitOpInFlight()) return;
2455
- var rect = track.getBoundingClientRect();
2456
- quickCommitDragState = {
2457
- pointerId: e.pointerId,
2458
- rect: rect,
2459
- moved: false,
2460
- action: "commit",
2533
+ function onDown(id) {
2534
+ return function(e) {
2535
+ if (chips[id].disabled || isQuickCommitOpInFlight()) return;
2536
+ drag = { anchor: id, pointerId: e.pointerId, startX: e.clientX, startY: e.clientY, moved: false, members: [id] };
2537
+ ORDER.forEach(function(m) { chips[m].classList.remove("qc-chip--anim"); });
2538
+ chips[id].classList.add("is-grabbing");
2539
+ try { chips[id].setPointerCapture(e.pointerId); } catch (err) { /* ignored */ }
2540
+ stage.classList.add("is-dragging");
2541
+ e.preventDefault();
2461
2542
  };
2462
- knob.setPointerCapture(e.pointerId);
2463
- track.classList.add("is-dragging");
2464
- updateQuickCommitDragVisual("commit", 0);
2465
- e.preventDefault();
2466
- };
2467
- var onPointerMove = function(e) {
2468
- if (!quickCommitDragState || quickCommitDragState.pointerId !== e.pointerId) return;
2469
- var rect = quickCommitDragState.rect;
2470
- var ratio = rect.width > 0 ? (e.clientX - rect.left) / rect.width : 0;
2471
- ratio = Math.max(0, Math.min(1, ratio));
2472
- if (Math.abs(e.clientX - rect.left) > 6) quickCommitDragState.moved = true;
2473
- var action = getQuickCommitActionFromRatio(ratio);
2474
- quickCommitDragState.action = action;
2475
- updateQuickCommitDragVisual(action, ratio);
2476
- };
2477
- var finish = function(e, cancelled) {
2478
- if (!quickCommitDragState) return;
2479
- var current = quickCommitDragState;
2480
- quickCommitDragState = null;
2481
- track.classList.remove("is-dragging");
2482
- try {
2483
- if (typeof knob.releasePointerCapture === "function") knob.releasePointerCapture(current.pointerId);
2484
- } catch (err) { /* ignored */ }
2485
- if (cancelled) {
2486
- updateQuickCommitDragVisual("commit");
2543
+ }
2544
+ function onMove(e) {
2545
+ if (!drag || drag.pointerId !== e.pointerId) return;
2546
+ if (Math.abs(e.clientX - drag.startX) > 3 || Math.abs(e.clientY - drag.startY) > 3) drag.moved = true;
2547
+ var fr = field.getBoundingClientRect();
2548
+ var fx = e.clientX - fr.left, fy = e.clientY - fr.top;
2549
+ // magnetic pickup: any loose chip whose home center is near the pointer joins the cluster.
2550
+ ORDER.forEach(function(id) {
2551
+ if (drag.members.indexOf(id) >= 0) return;
2552
+ var hx = home[id].x + cw(id) / 2, hy = home[id].y + chH() / 2;
2553
+ var dx = fx - hx, dy = fy - hy;
2554
+ if (Math.sqrt(dx * dx + dy * dy) < QC_DOCK_PICKUP_R) {
2555
+ drag.members.push(id);
2556
+ chips[id].classList.remove("qc-chip--anim");
2557
+ chips[id].classList.add("is-attached");
2558
+ }
2559
+ });
2560
+ var box = layoutCluster(drag.members, fx, fy);
2561
+ var hot = pointInLaunch(e.clientX, e.clientY);
2562
+ stage.setAttribute("data-hot", hot ? "1" : "0");
2563
+ stage.setAttribute("data-action", clusterAction(drag.members));
2564
+ if (drag.members.length > 1) showCluster(box); else hideCluster();
2565
+ setLaunchLabel(hot ? "松手执行" : "提交");
2566
+ }
2567
+ function endDrag(e, cancelled) {
2568
+ if (!drag || drag.pointerId !== e.pointerId) return;
2569
+ var cur = drag; drag = null;
2570
+ stage.classList.remove("is-dragging");
2571
+ stage.setAttribute("data-hot", "0");
2572
+ setLaunchLabel("提交");
2573
+ hideCluster();
2574
+ ORDER.forEach(function(m) { chips[m].classList.remove("is-grabbing", "is-attached"); });
2575
+ try { chips[cur.anchor].releasePointerCapture(cur.pointerId); } catch (err) { /* ignored */ }
2576
+
2577
+ if (!cancelled && !cur.moved) {
2578
+ // plain tap → fire this chip's own action
2579
+ submitQuickCommit(qcChipTapAction(cur.anchor));
2487
2580
  return;
2488
2581
  }
2489
- var action = current.moved ? current.action : "commit";
2490
- updateQuickCommitDragVisual(action);
2491
- submitQuickCommit(action);
2492
- if (e && typeof e.preventDefault === "function") e.preventDefault();
2493
- };
2494
- var onPointerUp = function(e) {
2495
- if (!quickCommitDragState || quickCommitDragState.pointerId !== e.pointerId) return;
2496
- finish(e, false);
2497
- };
2498
- var onPointerCancel = function(e) {
2499
- if (!quickCommitDragState || quickCommitDragState.pointerId !== e.pointerId) return;
2500
- finish(e, true);
2501
- };
2502
- var onKeyDown = function(e) {
2503
- if (knob.disabled || isQuickCommitOpInFlight()) return;
2504
- if (e.key === "ArrowRight") {
2505
- e.preventDefault();
2506
- var next = state.quickCommitDragAction === "commit"
2507
- ? "commit-tag"
2508
- : (state.quickCommitDragAction === "commit-tag" ? "commit-tag-push" : "commit-tag-push");
2509
- updateQuickCommitDragVisual(next);
2510
- } else if (e.key === "ArrowLeft") {
2511
- e.preventDefault();
2512
- var prev = state.quickCommitDragAction === "commit-tag-push"
2513
- ? "commit-tag"
2514
- : (state.quickCommitDragAction === "commit-tag" ? "commit" : "commit");
2515
- updateQuickCommitDragVisual(prev);
2516
- } else if (e.key === "Enter" || e.key === " ") {
2517
- e.preventDefault();
2518
- submitQuickCommit(state.quickCommitDragAction || "commit");
2582
+ if (!cancelled && pointInLaunch(e.clientX, e.clientY)) {
2583
+ submitQuickCommit(clusterAction(cur.members));
2584
+ return;
2519
2585
  }
2520
- };
2586
+ // released loose → everyone springs back home
2587
+ stage.setAttribute("data-action", "commit");
2588
+ layoutHome(true);
2589
+ }
2590
+
2591
+ var onResize = function() { if (state.quickCommitOpen && !drag) layoutHome(false); };
2592
+ window.addEventListener("resize", onResize);
2593
+
2594
+ ORDER.forEach(function(id) {
2595
+ var c = chips[id];
2596
+ c.addEventListener("pointerdown", onDown(id));
2597
+ c.addEventListener("pointermove", onMove);
2598
+ c.addEventListener("pointerup", function(e) { endDrag(e, false); });
2599
+ c.addEventListener("pointercancel", function(e) { endDrag(e, true); });
2600
+ c.addEventListener("keydown", function(e) {
2601
+ if (c.disabled || isQuickCommitOpInFlight()) return;
2602
+ if (e.key === "Enter" || e.key === " " || e.key === "Spacebar") {
2603
+ e.preventDefault();
2604
+ submitQuickCommit(qcChipTapAction(id));
2605
+ }
2606
+ });
2607
+ });
2608
+
2609
+ launch.addEventListener("click", function() {
2610
+ if (launch.disabled || isQuickCommitOpInFlight() || drag) return;
2611
+ submitQuickCommit("commit");
2612
+ });
2521
2613
 
2522
- knob.addEventListener("pointerdown", onPointerDown);
2523
- knob.addEventListener("pointermove", onPointerMove);
2524
- knob.addEventListener("pointerup", onPointerUp);
2525
- knob.addEventListener("pointercancel", onPointerCancel);
2526
- knob.addEventListener("keydown", onKeyDown);
2527
2614
  quickCommitDragCleanup = function() {
2528
- knob.removeEventListener("pointerdown", onPointerDown);
2529
- knob.removeEventListener("pointermove", onPointerMove);
2530
- knob.removeEventListener("pointerup", onPointerUp);
2531
- knob.removeEventListener("pointercancel", onPointerCancel);
2532
- knob.removeEventListener("keydown", onKeyDown);
2533
- quickCommitDragState = null;
2615
+ window.removeEventListener("resize", onResize);
2616
+ drag = null;
2534
2617
  };
2535
2618
  }
2536
2619
 
@@ -2592,11 +2675,9 @@
2592
2675
  var withTag = meta.withTag;
2593
2676
  var userTag = withTag ? (form.tag || "").trim() : "";
2594
2677
  var message = (form.customMessage || "").trim();
2595
- if (!message) {
2596
- state.quickCommitError = "请填写提交信息,或点击「AI 生成」。";
2597
- rerenderQuickCommitModal();
2598
- return;
2599
- }
2678
+ // Auto-generate flow: empty commit message → ask backend to write one (autoMessage:true).
2679
+ // Empty tag (when withTag) → ask backend to derive one (autoTag:true). Both go in one round-trip.
2680
+ var autoMessage = !message;
2600
2681
  var before = {
2601
2682
  branch: (state.gitStatus || {}).branch || "",
2602
2683
  commitHash: (state.gitStatus || {}).lastCommit && (state.gitStatus || {}).lastCommit.shortHash
@@ -2608,13 +2689,14 @@
2608
2689
  tag: (state.gitStatus || {}).latestTag || "",
2609
2690
  };
2610
2691
  var payload = {
2611
- autoMessage: false,
2612
- customMessage: message,
2692
+ autoMessage: autoMessage,
2693
+ customMessage: autoMessage ? "" : message,
2613
2694
  tag: userTag,
2614
2695
  autoTag: !!(withTag && !userTag),
2615
2696
  push: !!meta.push
2616
2697
  };
2617
2698
  state.quickCommitSubmitting = true;
2699
+ state.quickCommitAutoGenerating = autoMessage || payload.autoTag;
2618
2700
  state.quickCommitError = "";
2619
2701
  state.quickCommitPushError = "";
2620
2702
  state.quickCommitResult = null;
@@ -2668,6 +2750,7 @@
2668
2750
  })
2669
2751
  .finally(function() {
2670
2752
  state.quickCommitSubmitting = false;
2753
+ state.quickCommitAutoGenerating = false;
2671
2754
  if (state.quickCommitOpen) rerenderQuickCommitModal();
2672
2755
  });
2673
2756
  }
@@ -2790,23 +2873,40 @@
2790
2873
  }
2791
2874
 
2792
2875
  function renderQuickCommitDragControl(hasChanges) {
2793
- var action = normalizeQuickCommitAction(state.quickCommitDragAction || "commit");
2794
- var meta = getQuickCommitActionMeta(action);
2795
2876
  var disabled = !hasChanges || isQuickCommitOpInFlight();
2796
- var label = state.quickCommitSubmitting ? "执行中..." : meta.label;
2797
- return '<div class="qc-drag-wrap">' +
2798
- '<div id="quick-commit-drag-track" class="qc-drag-track" data-action="' + escapeHtml(action) + '" style="--qc-progress: ' + (action === "commit-tag-push" ? "100%" : (action === "commit-tag" ? "50%" : "0%")) + ';">' +
2799
- '<div class="qc-drag-progress" aria-hidden="true"></div>' +
2800
- '<div class="qc-drag-stages" aria-hidden="true">' +
2801
- '<span class="qc-drag-stage is-active is-passed" data-qc-stage="commit">Commit</span>' +
2802
- '<span class="qc-drag-stage' + (action !== "commit" ? ' is-passed' : '') + (action === "commit-tag" ? ' is-active' : '') + '" data-qc-stage="commit-tag">Tag</span>' +
2803
- '<span class="qc-drag-stage' + (action === "commit-tag-push" ? ' is-active is-passed' : '') + '" data-qc-stage="commit-tag-push">Push</span>' +
2877
+ // Busy panel replaces the dock entirely while the request is in flight.
2878
+ if (state.quickCommitSubmitting) {
2879
+ var busyLabel = state.quickCommitAutoGenerating ? "AI 生成 + 提交中…" : "执行中…";
2880
+ return '<div class="qc-dock-wrap">' +
2881
+ '<div class="qc-dock-busy" role="status"><span class="qc-dock-busy-dot"></span>' + escapeHtml(busyLabel) + '</div>' +
2882
+ '</div>';
2883
+ }
2884
+ function chip(id, label) {
2885
+ return '<button type="button" class="qc-chip qc-chip--' + id + '"' +
2886
+ ' data-chip="' + id + '"' + (disabled ? ' disabled' : '') + '>' +
2887
+ '<span class="qc-chip-dot" aria-hidden="true"></span>' +
2888
+ '<span class="qc-chip-label">' + label + '</span>' +
2889
+ '</button>';
2890
+ }
2891
+ var hint = disabled
2892
+ ? (!hasChanges ? "工作区干净,无可提交" : "")
2893
+ : "拖一个去碰另一个会黏在一起 · 整串丢进 ▶ 执行组合 · 单击直接执行该项";
2894
+ return '<div class="qc-dock-wrap"' + (disabled ? ' data-disabled="1"' : '') + '>' +
2895
+ '<div id="qc-dock-stage" class="qc-dock-stage" data-action="commit" data-hot="0">' +
2896
+ '<div id="qc-dock-field" class="qc-dock-field">' +
2897
+ '<div id="qc-dock-cluster" class="qc-dock-cluster" aria-hidden="true"></div>' +
2898
+ chip("commit", "Commit") +
2899
+ chip("tag", "Tag") +
2900
+ chip("push", "Push") +
2804
2901
  '</div>' +
2805
- '<button id="quick-commit-drag-action" class="qc-drag-action" type="button"' + (disabled ? ' disabled' : '') + ' aria-label="' + escapeHtml(meta.verb) + '">' +
2806
- '<span id="quick-commit-drag-label">' + escapeHtml(label) + '</span>' +
2807
- '<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true"><path d="M5 12h14"/><path d="M13 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>' +
2902
+ '<button type="button" id="qc-dock-launch" class="qc-dock-launch"' + (disabled ? ' disabled' : '') + ' aria-label="执行提交">' +
2903
+ '<span class="qc-dock-launch-arrow" aria-hidden="true">' +
2904
+ '<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2.3" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h12M13 6l6 6-6 6"/></svg>' +
2905
+ '</span>' +
2906
+ '<span id="qc-dock-launch-label" class="qc-dock-launch-label">提交</span>' +
2808
2907
  '</button>' +
2809
2908
  '</div>' +
2909
+ '<div class="qc-dock-hint">' + escapeHtml(hint) + '</div>' +
2810
2910
  '</div>';
2811
2911
  }
2812
2912
 
@@ -4040,6 +4140,10 @@
4040
4140
  return window.innerWidth <= 768;
4041
4141
  }
4042
4142
 
4143
+ function shouldShowSessionsBackdrop() {
4144
+ return !!state.sessionsDrawerOpen && (isMobileLayout() || !state.sidebarPinned);
4145
+ }
4146
+
4043
4147
  function setFilePanelOpen(nextOpen) {
4044
4148
  state.filePanelOpen = nextOpen;
4045
4149
  try {
@@ -9722,7 +9826,7 @@
9722
9826
  drawer.classList.toggle("open", state.sessionsDrawerOpen);
9723
9827
  }
9724
9828
  if (backdrop) {
9725
- backdrop.classList.toggle("open", state.sessionsDrawerOpen);
9829
+ backdrop.classList.toggle("open", shouldShowSessionsBackdrop());
9726
9830
  }
9727
9831
  if (mainLayout) {
9728
9832
  mainLayout.classList.toggle("sidebar-open", state.sessionsDrawerOpen);
@@ -9737,17 +9841,14 @@
9737
9841
  function toggleSessionsDrawer() {
9738
9842
  var isMobile = isMobileLayout();
9739
9843
  if (!isMobile) {
9740
- // 桌面:呼出 = 常驻全尺寸;再次点击 = 完全收起(floating-toggle 重新出现)。
9741
- // 取消了独立的「图钉」概念,sidebarPinned 现在由呼出/关闭自动管理。
9742
- var willOpen = !state.sidebarPinned;
9743
- state.sidebarPinned = willOpen;
9844
+ // 桌面:hamburger 只负责临时打开/关闭;锁定常驻由图钉按钮控制。
9845
+ var willOpen = state.sidebarPinned ? false : !state.sessionsDrawerOpen;
9744
9846
  state.sessionsDrawerOpen = willOpen;
9745
9847
  if (willOpen) {
9746
9848
  // 桌面重新呼出默认回到全尺寸;窄条形态需用户主动点 collapse 按钮切换。
9747
9849
  state.sidebarCollapsed = false;
9748
9850
  writeStoredBoolean("wand-sidebar-collapsed", false);
9749
9851
  }
9750
- writeStoredBoolean("wand-sidebar-pinned", willOpen);
9751
9852
  writeStoredBoolean("wand-sidebar-open", state.sessionsDrawerOpen);
9752
9853
  updateLayoutState();
9753
9854
  scheduleTerminalRefitAfterPaddingTransition();
@@ -9788,6 +9889,19 @@
9788
9889
  updateLayoutState();
9789
9890
  }
9790
9891
 
9892
+ function closeTransientSessionsDrawer() {
9893
+ if (isMobileLayout()) {
9894
+ closeSessionsDrawer();
9895
+ return;
9896
+ }
9897
+ if (state.sidebarPinned || state.sidebarCollapsed || !state.sessionsDrawerOpen) return;
9898
+ closeSwipedItem();
9899
+ state.sessionsDrawerOpen = false;
9900
+ writeStoredBoolean("wand-sidebar-open", false);
9901
+ updateLayoutState();
9902
+ scheduleTerminalRefitAfterPaddingTransition();
9903
+ }
9904
+
9791
9905
  // 把"浮在内容上的 drawer/backdrop"关掉,但保留桌面常驻栏与窄条形态。
9792
9906
  // 用法:从 input focus / send 按钮 / 选中会话 / 新建会话回调里调,这些场
9793
9907
  // 景只想避免遮罩挡住内容,并不想撤掉用户主动开启的常驻侧栏。