@co0ontty/wand 1.43.1 → 1.43.3

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": "32e84686610853404017c6bab0df94a4819f86b2",
3
- "builtAt": "2026-05-31T02:55:15.921Z",
4
- "version": "1.43.1",
2
+ "commit": "928999824e2776170b08b37eac1884673b0c8b2d",
3
+ "builtAt": "2026-05-31T06:10:02.658Z",
4
+ "version": "1.43.3",
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">' +
@@ -2295,29 +2296,6 @@
2295
2296
  };
2296
2297
  }
2297
2298
 
2298
- // Knob starts at LEFT (Commit). Three zones, left → right:
2299
- // commit (0.00..0.32) — starting position
2300
- // tag (0.32..0.68) — dwell here ~600ms to "arm" the Tag latch
2301
- // push (0.68..1.00) — release here to push
2302
- function getQuickCommitZoneFromRatio(ratio) {
2303
- if (ratio <= 0.32) return "commit";
2304
- if (ratio >= 0.68) return "push";
2305
- return "tag";
2306
- }
2307
-
2308
- // tagArmed is only true if the user dwelled long enough in the Tag zone.
2309
- // Sliding straight through Tag → Push without dwelling = commit-push (skip Tag).
2310
- function composeQuickCommitAction(zone, tagArmed) {
2311
- if (zone === "push") return tagArmed ? "commit-tag-push" : "commit-push";
2312
- if (zone === "tag") return "commit-tag";
2313
- return "commit";
2314
- }
2315
-
2316
- // How long the user must hold the knob inside the Tag zone before the Tag latch arms.
2317
- // Keep in sync with `--qc-dwell-ms` in styles.css.
2318
- var QUICK_COMMIT_TAG_DWELL_MS = 1100;
2319
- var QUICK_COMMIT_ZONE_RANK = { commit: 0, tag: 1, push: 2 };
2320
-
2321
2299
  function openQuickCommitModal() {
2322
2300
  if (!state.selectedId) return;
2323
2301
  state.quickCommitOpen = true;
@@ -2448,236 +2426,194 @@
2448
2426
  attachQuickCommitDrag();
2449
2427
  }
2450
2428
 
2451
- // ratio: knob CENTER position as fraction of track [0..1]. 0 = left (Commit start), 1 = right (Push).
2452
- function updateQuickCommitDragVisual(action, ratio, opts) {
2453
- action = normalizeQuickCommitAction(action);
2454
- state.quickCommitDragAction = action;
2455
- var track = document.getElementById("quick-commit-drag-track");
2456
- if (!track) return;
2457
- var meta = getQuickCommitActionMeta(action);
2458
- // Resting position per action when user isn't dragging.
2459
- var defaultRatio = 0;
2460
- if (action === "commit-tag") defaultRatio = 0.5;
2461
- else if (action === "commit-push" || action === "commit-tag-push") defaultRatio = 1;
2462
- var progress = typeof ratio === "number"
2463
- ? Math.max(0, Math.min(1, ratio))
2464
- : defaultRatio;
2465
- var zone = getQuickCommitZoneFromRatio(progress);
2466
- track.setAttribute("data-action", action);
2467
- track.setAttribute("data-zone", zone);
2468
- track.setAttribute("data-tag-armed", (action === "commit-tag" || action === "commit-tag-push") ? "1" : "0");
2469
- track.style.setProperty("--qc-progress", (progress * 100).toFixed(1) + "%");
2470
- var knob = document.getElementById("quick-commit-drag-action");
2471
- if (knob) {
2472
- var trackWidth = track.clientWidth;
2473
- var knobWidth = knob.offsetWidth;
2474
- var pad = 6;
2475
- // Knob CENTER tracks ratio * trackWidth, clamped so the knob stays fully inside.
2476
- var center = progress * trackWidth;
2477
- var minLeft = pad;
2478
- var maxLeft = Math.max(pad, trackWidth - knobWidth - pad);
2479
- var left = Math.max(minLeft, Math.min(maxLeft, center - knobWidth / 2));
2480
- track.style.setProperty("--qc-knob-x", left.toFixed(1) + "px");
2481
- }
2482
- var label = document.getElementById("quick-commit-drag-label");
2483
- if (label) {
2484
- var dwell = track.getAttribute("data-tag-dwell") || "idle";
2485
- var cancelMode = track.getAttribute("data-cancel-mode") === "1";
2486
- var txt;
2487
- if (state.quickCommitSubmitting) {
2488
- txt = state.quickCommitAutoGenerating ? "AI 生成 + 提交中…" : "执行中…";
2489
- } else if (cancelMode) {
2490
- txt = "松手取消 ✕";
2491
- } else if (dwell === "active") {
2492
- txt = "锁定 Tag 中…";
2493
- } else if (dwell === "armed" && zone === "tag") {
2494
- txt = "Tag 已就绪 · 继续推送 →";
2495
- } else {
2496
- txt = meta.label;
2497
- }
2498
- label.textContent = txt;
2499
- }
2500
- // Stage class refresh.
2501
- var stages = track.querySelectorAll("[data-qc-stage]");
2502
- for (var i = 0; i < stages.length; i++) {
2503
- var st = stages[i].getAttribute("data-qc-stage");
2504
- var active = false, passed = false;
2505
- if (st === "commit") {
2506
- active = action === "commit";
2507
- passed = true;
2508
- } else if (st === "tag") {
2509
- active = action === "commit-tag";
2510
- passed = action === "commit-tag" || action === "commit-tag-push";
2511
- } else if (st === "push") {
2512
- active = action === "commit-push" || action === "commit-tag-push";
2513
- passed = active;
2514
- }
2515
- stages[i].classList.toggle("is-active", active);
2516
- stages[i].classList.toggle("is-passed", passed);
2517
- }
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";
2518
2438
  }
2519
2439
 
2520
- function attachQuickCommitDrag() {
2521
- var track = document.getElementById("quick-commit-drag-track");
2522
- var knob = document.getElementById("quick-commit-drag-action");
2523
- if (!track || !knob) return;
2524
- updateQuickCommitDragVisual(state.quickCommitDragAction || "commit");
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;
2525
2443
 
2526
- function setDwell(state) { track.setAttribute("data-tag-dwell", state); }
2527
- function clearArmTimer() {
2528
- if (quickCommitDragState && quickCommitDragState.tagArmTimer) {
2529
- clearTimeout(quickCommitDragState.tagArmTimer);
2530
- quickCommitDragState.tagArmTimer = null;
2531
- }
2532
- }
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";
2449
+ }
2533
2450
 
2534
- function setCancelMode(on) { track.setAttribute("data-cancel-mode", on ? "1" : "0"); }
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.
2456
+ function attachQuickCommitDrag() {
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
+ }
2535
2532
 
2536
- var onPointerDown = function(e) {
2537
- if (knob.disabled || isQuickCommitOpInFlight()) return;
2538
- var rect = track.getBoundingClientRect();
2539
- quickCommitDragState = {
2540
- pointerId: e.pointerId,
2541
- rect: rect,
2542
- startX: e.clientX,
2543
- moved: false,
2544
- zone: "commit",
2545
- maxZone: "commit", // furthest zone the knob ever reached this drag
2546
- tagArmed: false,
2547
- tagArmTimer: null,
2548
- 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();
2549
2542
  };
2550
- try { knob.setPointerCapture(e.pointerId); } catch (err) { /* ignored */ }
2551
- track.classList.add("is-dragging");
2552
- setDwell("idle");
2553
- setCancelMode(false);
2554
- updateQuickCommitDragVisual("commit", 0);
2555
- e.preventDefault();
2556
- };
2557
- var onPointerMove = function(e) {
2558
- if (!quickCommitDragState || quickCommitDragState.pointerId !== e.pointerId) return;
2559
- var rect = quickCommitDragState.rect;
2560
- var ratio = rect.width > 0 ? (e.clientX - rect.left) / rect.width : 0;
2561
- ratio = Math.max(0, Math.min(1, ratio));
2562
- if (Math.abs(e.clientX - quickCommitDragState.startX) > 5) quickCommitDragState.moved = true;
2563
- var zone = getQuickCommitZoneFromRatio(ratio);
2564
- // Track furthest zone reached so we can detect a backtrack-to-commit.
2565
- if (QUICK_COMMIT_ZONE_RANK[zone] > QUICK_COMMIT_ZONE_RANK[quickCommitDragState.maxZone]) {
2566
- quickCommitDragState.maxZone = zone;
2567
- }
2568
- // Zone transitions drive the dwell state machine.
2569
- if (zone !== quickCommitDragState.zone) {
2570
- if (zone === "tag" && !quickCommitDragState.tagArmed) {
2571
- // Entered Tag zone unarmed → kick off the dwell timer + CSS fill animation.
2572
- clearArmTimer();
2573
- setDwell("active");
2574
- quickCommitDragState.tagArmTimer = setTimeout(function() {
2575
- if (!quickCommitDragState) return;
2576
- quickCommitDragState.tagArmed = true;
2577
- quickCommitDragState.tagArmTimer = null;
2578
- setDwell("armed");
2579
- var newAction = composeQuickCommitAction(quickCommitDragState.zone, true);
2580
- quickCommitDragState.action = newAction;
2581
- updateQuickCommitDragVisual(newAction);
2582
- }, QUICK_COMMIT_TAG_DWELL_MS);
2583
- } else if (zone !== "tag" && !quickCommitDragState.tagArmed) {
2584
- // Left Tag zone before arming → cancel the dwell.
2585
- clearArmTimer();
2586
- setDwell("idle");
2587
- } else if (zone === "tag" && quickCommitDragState.tagArmed) {
2588
- setDwell("armed");
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");
2589
2558
  }
2590
- quickCommitDragState.zone = zone;
2591
- }
2592
- // Cancel-mode: backed back to commit after venturing further.
2593
- // Visual cue is set here; the actual "no-submit" decision is made in finish().
2594
- var inCancelMode = quickCommitDragState.maxZone !== "commit" && zone === "commit";
2595
- setCancelMode(inCancelMode);
2596
- var action = composeQuickCommitAction(zone, quickCommitDragState.tagArmed);
2597
- quickCommitDragState.action = action;
2598
- updateQuickCommitDragVisual(action, ratio);
2599
- };
2600
- var finish = function(e, cancelled) {
2601
- if (!quickCommitDragState) return;
2602
- var current = quickCommitDragState;
2603
- clearArmTimer();
2604
- quickCommitDragState = null;
2605
- track.classList.remove("is-dragging");
2606
- setDwell("idle");
2607
- setCancelMode(false);
2608
- try {
2609
- if (typeof knob.releasePointerCapture === "function") knob.releasePointerCapture(current.pointerId);
2610
- } catch (err) { /* ignored */ }
2611
- // Backtrack: user ventured out past Commit, then dragged back and released at Commit → cancel.
2612
- var isBacktrack = current.moved
2613
- && current.zone === "commit"
2614
- && current.maxZone !== "commit";
2615
- if (cancelled || isBacktrack) {
2616
- updateQuickCommitDragVisual("commit");
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));
2617
2580
  return;
2618
2581
  }
2619
- // Tap (no drag) just commit. Auto-message will fire if the message is empty.
2620
- var action = current.moved
2621
- ? composeQuickCommitAction(current.zone, current.tagArmed)
2622
- : "commit";
2623
- updateQuickCommitDragVisual(action);
2624
- submitQuickCommit(action);
2625
- if (e && typeof e.preventDefault === "function") e.preventDefault();
2626
- };
2627
- var onPointerUp = function(e) {
2628
- if (!quickCommitDragState || quickCommitDragState.pointerId !== e.pointerId) return;
2629
- finish(e, false);
2630
- };
2631
- var onPointerCancel = function(e) {
2632
- if (!quickCommitDragState || quickCommitDragState.pointerId !== e.pointerId) return;
2633
- finish(e, true);
2634
- };
2635
- // Keyboard parity:
2636
- // → step zone right (commit → commit-tag → commit-tag-push). The first → into Tag arms it instantly.
2637
- // ← step zone left
2638
- // Home → reset to commit
2639
- // Enter / Space → submit current action
2640
- var onKeyDown = function(e) {
2641
- if (knob.disabled || isQuickCommitOpInFlight()) return;
2642
- var cur = state.quickCommitDragAction || "commit";
2643
- if (e.key === "ArrowRight") {
2644
- e.preventDefault();
2645
- var next = cur;
2646
- if (cur === "commit") next = "commit-tag"; // arm Tag
2647
- else if (cur === "commit-tag") next = "commit-tag-push";
2648
- else if (cur === "commit-push") next = "commit-push";
2649
- updateQuickCommitDragVisual(next);
2650
- } else if (e.key === "ArrowLeft") {
2651
- e.preventDefault();
2652
- var prev = cur;
2653
- if (cur === "commit-tag-push") prev = "commit-tag";
2654
- else if (cur === "commit-tag") prev = "commit";
2655
- else if (cur === "commit-push") prev = "commit";
2656
- updateQuickCommitDragVisual(prev);
2657
- } else if (e.key === "Home") {
2658
- e.preventDefault();
2659
- updateQuickCommitDragVisual("commit");
2660
- } else if (e.key === "Enter" || e.key === " ") {
2661
- e.preventDefault();
2662
- submitQuickCommit(state.quickCommitDragAction || "commit");
2582
+ if (!cancelled && pointInLaunch(e.clientX, e.clientY)) {
2583
+ submitQuickCommit(clusterAction(cur.members));
2584
+ return;
2663
2585
  }
2664
- };
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
+ });
2665
2613
 
2666
- knob.addEventListener("pointerdown", onPointerDown);
2667
- knob.addEventListener("pointermove", onPointerMove);
2668
- knob.addEventListener("pointerup", onPointerUp);
2669
- knob.addEventListener("pointercancel", onPointerCancel);
2670
- knob.addEventListener("keydown", onKeyDown);
2671
2614
  quickCommitDragCleanup = function() {
2672
- knob.removeEventListener("pointerdown", onPointerDown);
2673
- knob.removeEventListener("pointermove", onPointerMove);
2674
- knob.removeEventListener("pointerup", onPointerUp);
2675
- knob.removeEventListener("pointercancel", onPointerCancel);
2676
- knob.removeEventListener("keydown", onKeyDown);
2677
- if (quickCommitDragState && quickCommitDragState.tagArmTimer) {
2678
- clearTimeout(quickCommitDragState.tagArmTimer);
2679
- }
2680
- quickCommitDragState = null;
2615
+ window.removeEventListener("resize", onResize);
2616
+ drag = null;
2681
2617
  };
2682
2618
  }
2683
2619
 
@@ -2937,45 +2873,40 @@
2937
2873
  }
2938
2874
 
2939
2875
  function renderQuickCommitDragControl(hasChanges) {
2940
- var action = normalizeQuickCommitAction(state.quickCommitDragAction || "commit");
2941
- var meta = getQuickCommitActionMeta(action);
2942
2876
  var disabled = !hasChanges || isQuickCommitOpInFlight();
2943
- var label = state.quickCommitSubmitting ? "执行中…" : meta.label;
2944
- // Stage classes derived from the current action.
2945
- var commitActive = action === "commit";
2946
- var tagActive = action === "commit-tag";
2947
- var tagPassed = action === "commit-tag" || action === "commit-tag-push";
2948
- var pushActive = action === "commit-push" || action === "commit-tag-push";
2949
- var hint = "向右拖 · 经过 Tag 时停一下打 Tag · 直接划到 Push 跳过 Tag · 留空会自动生成";
2950
- return '<div class="qc-drag-wrap">' +
2951
- '<div id="quick-commit-drag-track" class="qc-drag-track" data-action="' + escapeHtml(action) + '" data-zone="commit" data-tag-dwell="idle">' +
2952
- // Baseline track: subtle grey rail under everything, segments brighten as the knob passes.
2953
- '<svg class="qc-drag-baseline" viewBox="0 0 100 24" preserveAspectRatio="none" aria-hidden="true">' +
2954
- '<line class="qc-baseline-rail" x1="4" y1="12" x2="96" y2="12" />' +
2955
- '<line class="qc-baseline-seg qc-baseline-seg--left" x1="4" y1="12" x2="50" y2="12" />' +
2956
- '<line class="qc-baseline-seg qc-baseline-seg--right" x1="50" y1="12" x2="96" y2="12" />' +
2957
- '</svg>' +
2958
- // Three marching chevrons running right — "drag this way".
2959
- '<div class="qc-chevrons" aria-hidden="true">' +
2960
- '<svg class="qc-chevron" viewBox="0 0 12 16" width="9" height="12"><path d="M3 2 L9 8 L3 14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>' +
2961
- '<svg class="qc-chevron" viewBox="0 0 12 16" width="9" height="12"><path d="M3 2 L9 8 L3 14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>' +
2962
- '<svg class="qc-chevron" viewBox="0 0 12 16" width="9" height="12"><path d="M3 2 L9 8 L3 14" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>' +
2963
- '</div>' +
2964
- // Stage dots / labels along the rail.
2965
- '<div class="qc-drag-stages" aria-hidden="true">' +
2966
- '<span class="qc-drag-stage qc-stage-commit' + (commitActive ? ' is-active' : '') + ' is-passed" data-qc-stage="commit">Commit</span>' +
2967
- '<span class="qc-drag-stage qc-stage-tag' + (tagActive ? ' is-active' : '') + (tagPassed ? ' is-passed' : '') + '" data-qc-stage="tag">Tag</span>' +
2968
- '<span class="qc-drag-stage qc-stage-push' + (pushActive ? ' is-active is-passed' : '') + '" data-qc-stage="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") +
2969
2901
  '</div>' +
2970
- // Knob starts at left, slides right.
2971
- '<button id="quick-commit-drag-action" class="qc-drag-action" type="button"' + (disabled ? ' disabled' : '') + ' aria-label="' + escapeHtml(meta.verb) + '" title="' + escapeHtml(hint) + '">' +
2972
- '<span class="qc-drag-grip" aria-hidden="true"><i></i><i></i><i></i></span>' +
2973
- '<span id="quick-commit-drag-label" class="qc-drag-label">' + escapeHtml(label) + '</span>' +
2974
- // Bottom progress bar — fills 0→100% over DWELL_MS while sitting in the Tag zone.
2975
- '<span class="qc-drag-dwell-bar" aria-hidden="true"></span>' +
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>' +
2976
2907
  '</button>' +
2977
2908
  '</div>' +
2978
- '<div class="qc-drag-help" aria-hidden="true">' + escapeHtml(hint) + '</div>' +
2909
+ '<div class="qc-dock-hint">' + escapeHtml(hint) + '</div>' +
2979
2910
  '</div>';
2980
2911
  }
2981
2912
 
@@ -4209,6 +4140,10 @@
4209
4140
  return window.innerWidth <= 768;
4210
4141
  }
4211
4142
 
4143
+ function shouldShowSessionsBackdrop() {
4144
+ return !!state.sessionsDrawerOpen && (isMobileLayout() || !state.sidebarPinned);
4145
+ }
4146
+
4212
4147
  function setFilePanelOpen(nextOpen) {
4213
4148
  state.filePanelOpen = nextOpen;
4214
4149
  try {
@@ -9891,7 +9826,7 @@
9891
9826
  drawer.classList.toggle("open", state.sessionsDrawerOpen);
9892
9827
  }
9893
9828
  if (backdrop) {
9894
- backdrop.classList.toggle("open", state.sessionsDrawerOpen);
9829
+ backdrop.classList.toggle("open", shouldShowSessionsBackdrop());
9895
9830
  }
9896
9831
  if (mainLayout) {
9897
9832
  mainLayout.classList.toggle("sidebar-open", state.sessionsDrawerOpen);
@@ -9906,17 +9841,14 @@
9906
9841
  function toggleSessionsDrawer() {
9907
9842
  var isMobile = isMobileLayout();
9908
9843
  if (!isMobile) {
9909
- // 桌面:呼出 = 常驻全尺寸;再次点击 = 完全收起(floating-toggle 重新出现)。
9910
- // 取消了独立的「图钉」概念,sidebarPinned 现在由呼出/关闭自动管理。
9911
- var willOpen = !state.sidebarPinned;
9912
- state.sidebarPinned = willOpen;
9844
+ // 桌面:hamburger 只负责临时打开/关闭;锁定常驻由图钉按钮控制。
9845
+ var willOpen = state.sidebarPinned ? false : !state.sessionsDrawerOpen;
9913
9846
  state.sessionsDrawerOpen = willOpen;
9914
9847
  if (willOpen) {
9915
9848
  // 桌面重新呼出默认回到全尺寸;窄条形态需用户主动点 collapse 按钮切换。
9916
9849
  state.sidebarCollapsed = false;
9917
9850
  writeStoredBoolean("wand-sidebar-collapsed", false);
9918
9851
  }
9919
- writeStoredBoolean("wand-sidebar-pinned", willOpen);
9920
9852
  writeStoredBoolean("wand-sidebar-open", state.sessionsDrawerOpen);
9921
9853
  updateLayoutState();
9922
9854
  scheduleTerminalRefitAfterPaddingTransition();
@@ -9957,6 +9889,19 @@
9957
9889
  updateLayoutState();
9958
9890
  }
9959
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
+
9960
9905
  // 把"浮在内容上的 drawer/backdrop"关掉,但保留桌面常驻栏与窄条形态。
9961
9906
  // 用法:从 input focus / send 按钮 / 选中会话 / 新建会话回调里调,这些场
9962
9907
  // 景只想避免遮罩挡住内容,并不想撤掉用户主动开启的常驻侧栏。