dsh-git-branch-manage 1.0.4 → 1.0.5

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.
package/lib/client.js CHANGED
@@ -44,6 +44,36 @@ window.__ModuleLoader__.load({
44
44
  if (/(^|\/)[^/]+\.lock($|\/)/.test(name)) return "illegal";
45
45
  return null;
46
46
  }
47
+ /**
48
+ * Shipped default for the cn-accelerate mirror prefix (the design doc's
49
+ * `cnAccelerateUrl` default and the popover's「恢复默认」target).
50
+ */
51
+ const ACCEL_DEFAULT_URL = "https://gh-proxy.com/";
52
+ /**
53
+ * Pre-flight check of the cn-accelerate prefix against what the rewrite can
54
+ * actually use: non-empty after trim, `http://` or `https://` scheme, and no
55
+ * whitespace anywhere (a space would break the `url.<base>.insteadOf=<target>`
56
+ * git config value and smuggle a second argument).
57
+ * @param value - raw draft text.
58
+ * @returns the issue kind, or undefined when the value is acceptable.
59
+ */
60
+ function accelerateUrlError(value) {
61
+ const trimmed = value.trim();
62
+ if (trimmed === "") return "empty";
63
+ if (/\s/.test(trimmed)) return "invalid";
64
+ if (!trimmed.startsWith("http://") && !trimmed.startsWith("https://")) return "invalid";
65
+ }
66
+ /**
67
+ * Normalize an accepted accelerate URL: trim, and append the trailing `/`
68
+ * the prefix concatenation requires (`https://gh-proxy.com/` + `o/r.git`
69
+ * must stay one valid URL). Callers pass the result to `url.<base>.insteadOf`.
70
+ * @param value - an accepted (non-empty, http(s), whitespace-free) URL.
71
+ * @returns the prefix ending in `/`.
72
+ */
73
+ function normalizeAccelerateUrl(value) {
74
+ const trimmed = value.trim();
75
+ return trimmed.endsWith("/") ? trimmed : `${trimmed}/`;
76
+ }
47
77
  //#endregion
48
78
  //#region src/wire.ts
49
79
  /**
@@ -3401,6 +3431,151 @@ window.__ModuleLoader__.load({
3401
3431
  }
3402
3432
  };
3403
3433
  //#endregion
3434
+ //#region src/client/accel-form.ts
3435
+ /** The fields this form edits. */
3436
+ const ACCEL_ENABLED_FIELD = "cnAccelerate";
3437
+ const ACCEL_URL_FIELD = "cnAccelerateUrl";
3438
+ /**
3439
+ * Stages the cn-accelerate edits over the `dsh-git-branch` Host entry.
3440
+ *
3441
+ * Publishes through a snapshot store like {@link CardForm}: the consumer
3442
+ * reads through a snapshot selector while the scope and the local drafts
3443
+ * change underneath; every projection is rebuilt from the two together.
3444
+ */
3445
+ var AccelForm = class {
3446
+ scope;
3447
+ snapshotValue;
3448
+ listeners = /* @__PURE__ */ new Set();
3449
+ enabledDraft;
3450
+ urlDraft;
3451
+ saving = false;
3452
+ failed = false;
3453
+ /**
3454
+ * @param scope - the bound configuration form for the `dsh-git-branch` Host entry.
3455
+ */
3456
+ constructor(scope) {
3457
+ this.scope = scope;
3458
+ this.snapshotValue = this.project();
3459
+ scope.subscribe(() => {
3460
+ this.publish();
3461
+ });
3462
+ }
3463
+ /** @returns the store a consumer reads through its bound selector. */
3464
+ bind() {
3465
+ return {
3466
+ getSnapshot: () => this.snapshotValue,
3467
+ subscribe: (listener) => {
3468
+ this.listeners.add(listener);
3469
+ return () => {
3470
+ this.listeners.delete(listener);
3471
+ };
3472
+ },
3473
+ set: (next) => {
3474
+ this.store(next);
3475
+ }
3476
+ };
3477
+ }
3478
+ /** @returns the stage, restore, save, and discard actions bound to this form. */
3479
+ actions() {
3480
+ return {
3481
+ setAccelEnabled: (value) => {
3482
+ this.enabledDraft = value;
3483
+ this.failed = false;
3484
+ this.publish();
3485
+ },
3486
+ editAccelUrl: (text) => {
3487
+ this.urlDraft = text;
3488
+ this.failed = false;
3489
+ this.publish();
3490
+ },
3491
+ restoreAccelDefault: () => {
3492
+ this.urlDraft = ACCEL_DEFAULT_URL;
3493
+ this.failed = false;
3494
+ this.publish();
3495
+ },
3496
+ saveAccel: () => this.save(),
3497
+ discardAccel: () => {
3498
+ if (this.enabledDraft === void 0 && this.urlDraft === void 0 && !this.failed) return;
3499
+ this.enabledDraft = void 0;
3500
+ this.urlDraft = void 0;
3501
+ this.failed = false;
3502
+ this.publish();
3503
+ }
3504
+ };
3505
+ }
3506
+ /** The URL as the user sees it: the live draft, else the stored value over
3507
+ * the shipped default. */
3508
+ effectiveUrlText() {
3509
+ const value = this.scope.getSnapshot().value?.[ACCEL_URL_FIELD];
3510
+ return this.urlDraft ?? value ?? "https://gh-proxy.com/";
3511
+ }
3512
+ /**
3513
+ * Write the switch and the normalized URL TOGETHER, then re-seed from what
3514
+ * the Host accepted. The URL is validated first — an unusable draft blocks
3515
+ * the save (and the surfaces disable the button on the same error), so the
3516
+ * switch can never land alone against a mirror the user has not confirmed.
3517
+ * A save that did not land keeps its drafts so the user can correct them.
3518
+ */
3519
+ async save() {
3520
+ if (this.enabledDraft === void 0 && this.urlDraft === void 0 || this.saving) return;
3521
+ const urlText = this.effectiveUrlText();
3522
+ if (accelerateUrlError(urlText) !== void 0) return;
3523
+ const intendedEnabled = this.enabledDraft ?? this.scope.getSnapshot().value?.["cnAccelerate"] ?? false;
3524
+ const intendedUrl = normalizeAccelerateUrl(urlText);
3525
+ this.saving = true;
3526
+ this.failed = false;
3527
+ this.publish();
3528
+ let landed = true;
3529
+ try {
3530
+ await this.scope.set(ACCEL_ENABLED_FIELD, intendedEnabled);
3531
+ await this.scope.set(ACCEL_URL_FIELD, intendedUrl);
3532
+ const user = this.userLayer();
3533
+ if (user?.["cnAccelerate"] !== intendedEnabled || user?.["cnAccelerateUrl"] !== intendedUrl) landed = false;
3534
+ } catch (_settingsWriteFailure) {
3535
+ landed = false;
3536
+ }
3537
+ if (landed) {
3538
+ this.enabledDraft = void 0;
3539
+ this.urlDraft = void 0;
3540
+ }
3541
+ this.saving = false;
3542
+ this.failed = !landed;
3543
+ this.publish();
3544
+ }
3545
+ /** The raw user layer narrowed to a record; the wire answer is `unknown`. */
3546
+ userLayer() {
3547
+ const user = this.scope.getSnapshot().user;
3548
+ return typeof user === "object" && user !== null ? user : void 0;
3549
+ }
3550
+ project() {
3551
+ const snapshot = this.scope.getSnapshot();
3552
+ const storedEnabled = snapshot.value?.["cnAccelerate"] ?? false;
3553
+ const storedUrl = snapshot.value?.["cnAccelerateUrl"] ?? "https://gh-proxy.com/";
3554
+ const urlText = this.effectiveUrlText();
3555
+ const enabledDirty = this.enabledDraft !== void 0 && this.enabledDraft !== storedEnabled;
3556
+ const urlDirty = this.urlDraft !== void 0 && this.urlDraft !== storedUrl;
3557
+ return {
3558
+ available: snapshot.status === "ready",
3559
+ writable: snapshot.writable,
3560
+ enabled: this.enabledDraft ?? storedEnabled,
3561
+ urlText,
3562
+ dirty: enabledDirty || urlDirty,
3563
+ saving: this.saving,
3564
+ failed: this.failed,
3565
+ urlError: accelerateUrlError(urlText) ?? null
3566
+ };
3567
+ }
3568
+ /** Replace the snapshot reference and notify, only when the fact moved. */
3569
+ store(next) {
3570
+ if (next === this.snapshotValue) return;
3571
+ this.snapshotValue = next;
3572
+ for (const listener of this.listeners) listener();
3573
+ }
3574
+ publish() {
3575
+ this.store(this.project());
3576
+ }
3577
+ };
3578
+ //#endregion
3404
3579
  //#region src/client/git-panel-data.ts
3405
3580
  /**
3406
3581
  * Classify raw %D decoration segments: `HEAD -> main` (and bare `HEAD`) is
@@ -3565,7 +3740,7 @@ window.__ModuleLoader__.load({
3565
3740
  }
3566
3741
  //#endregion
3567
3742
  //#region \0git-worktree-css:/home/runner/work/dsh-git-branch/dsh-git-branch/src/client/GitHistoryPanel.module.css?inline
3568
- const css$2 = "/* Git panel (sidebar global panel). Three stacked sections over the main\n * column: uncommitted changes + commit box, branches, commit history. Host\n * tokens only; the few status colors fall back to the same literals the\n * settings card uses. */\n\n.page_427795137 {\n display: flex;\n flex-direction: column;\n min-height: 0;\n height: 100%;\n}\n\n.topbar_427795137 {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 16px 8px;\n}\n\n.back_427795137 {\n flex: none;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n border: 0;\n background: none;\n cursor: pointer;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n padding: 4px 8px;\n border-radius: 8px;\n}\n\n.back_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n.title_427795137 {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n.repoPill_427795137 {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n font-size: 12px;\n line-height: 1.4;\n color: var(--dsw-alias-label-secondary);\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 999px;\n padding: 3px 10px;\n white-space: nowrap;\n overflow: hidden;\n}\n\n.repoName_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.branchName_427795137 {\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n.track_427795137 {\n font-variant-numeric: tabular-nums;\n}\n\n.topbarActions_427795137 {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 6px;\n flex: none;\n}\n\n.refreshBtn_427795137 {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: 0;\n border-radius: 8px;\n background: none;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n}\n\n.refreshBtn_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n.refreshBtn_427795137[disabled] {\n opacity: 0.5;\n cursor: default;\n}\n\n.scroll_427795137 {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n padding: 0 16px 16px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n/* The page column below the topbar is a definite-height flex column and the\n * three cards split it 2:1:2 (uncommitted : branches : history) — five\n * shares, the topbar and gaps taking their natural height first. Each body\n * scrolls INSIDE its share — min-height: 0 is what lets a flex child shrink\n * below its content (the old natural-height model painted overflowing rows\n * over the sections below). A floor keeps a very short panel from\n * collapsing a card to its bare head; past the floor the column itself\n * scrolls. */\n.section_427795137 {\n border: 0.5px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-2);\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n\n.sectionFill_427795137 {\n flex: 1 1 0;\n min-height: 84px;\n}\n\n/* The three flex-grow overrides of the 2:1:2 split. */\n.sectionChanges_427795137 {\n flex-grow: 2;\n}\n\n.sectionBranches_427795137 {\n flex-grow: 1;\n}\n\n.sectionHistory_427795137 {\n flex-grow: 2;\n}\n\n.sectionHead_427795137 {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n.sectionTitle_427795137 {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n.countBadge_427795137 {\n font-size: 10px;\n line-height: 1;\n border-radius: 999px;\n padding: 3px 7px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-secondary);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n.sectionTools_427795137 {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.sectionBody_427795137 {\n padding: 10px 12px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n flex: 1 1 auto;\n min-height: 0;\n}\n\n/* The branch and history bodies scroll as a whole inside their share. */\n.sectionBodyScroll_427795137 {\n overflow-y: auto;\n overscroll-behavior: contain;\n}\n\n/* Inside the uncommitted card only the file lists scroll; the commit box\n * below stays pinned however long the change tree is. */\n.changeList_427795137 {\n display: flex;\n flex-direction: column;\n gap: 8px;\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n overscroll-behavior: contain;\n}\n\n/* The 全部展开/全部折叠 text chips in the branch card head. */\n.treeToggle_427795137 {\n background: none;\n border: 0;\n padding: 2px 5px;\n font: inherit;\n font-size: 11.5px;\n line-height: 1.4;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n border-radius: 5px;\n white-space: nowrap;\n}\n\n.treeToggle_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-brand-primary);\n}\n\n/* Small action chip used across the section heads and the commit row. */\n.act_427795137 {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n font: inherit;\n font-size: 12px;\n line-height: 1.4;\n cursor: pointer;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 7px;\n padding: 4px 10px;\n white-space: nowrap;\n}\n\n.act_427795137:hover:not([disabled]) {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.act_427795137[disabled] {\n opacity: 0.45;\n cursor: default;\n}\n\n.actPrimary_427795137 {\n /* Hardcoded blue by request — the brand token does not resolve reliably\n * inside the better-sidebar panel. */\n background: #4c6bf5;\n border-color: transparent;\n color: #fff;\n}\n\n.actPrimary_427795137:hover:not([disabled]) {\n border-color: transparent;\n filter: brightness(1.06);\n}\n\n/* Uncommitted changes */\n\n.groupTitleRow_427795137 {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.linkBtn_427795137 {\n margin-left: auto;\n background: none;\n border: 0;\n padding: 2px 4px;\n font: inherit;\n font-size: 12px;\n color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n border-radius: 5px;\n}\n\n.linkBtn_427795137:hover:not([disabled]) {\n text-decoration: underline;\n}\n\n.linkBtn_427795137[disabled] {\n opacity: 0.5;\n cursor: default;\n}\n\n.changeRow_427795137 {\n display: grid;\n grid-template-columns: 16px 18px minmax(0, 1fr) auto;\n gap: 8px;\n align-items: center;\n padding: 3px 6px;\n border-radius: 6px;\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n}\n\n.changeRow_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.changeCheck_427795137 {\n width: 14px;\n height: 14px;\n accent-color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n margin: 0;\n}\n\n.statusBadge_427795137 {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n font-size: 10px;\n line-height: 1;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-weight: 700;\n color: #fff;\n box-sizing: border-box;\n}\n\n.statusA_427795137 { background: var(--dsw-alias-status-success, #218358); }\n.statusM_427795137 { background: var(--dsw-alias-label-warning, #b07000); }\n.statusD_427795137 { background: var(--dsw-alias-status-danger, #d5304f); }\n.statusR_427795137 { background: #8350d8; }\n.statusC_427795137 { background: #8350d8; }\n.statusT_427795137 { background: #5b7a9d; }\n.statusQ_427795137 { background: var(--dsw-alias-label-dimmed, #9aa0aa); }\n.statusU_427795137 { background: #d5502e; }\n\n.changePath_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: ui-monospace, Consolas, monospace;\n font-size: 11.5px;\n direction: rtl;\n text-align: left;\n}\n\n.stageHint_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n flex: none;\n}\n\n.commitBox_427795137 {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 4px;\n flex: none;\n}\n\n.commitInput_427795137 {\n width: 100%;\n box-sizing: border-box;\n resize: vertical;\n min-height: 52px;\n font: inherit;\n font-size: 12.5px;\n line-height: 1.5;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n padding: 7px 10px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n outline: none;\n}\n\n.commitInput_427795137:focus {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.commitActions_427795137 {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.commitHint_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n min-height: 16px;\n}\n\n.split_427795137 {\n margin-left: auto;\n display: flex;\n position: relative;\n}\n\n.splitMain_427795137 {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n padding: 6px 16px;\n font-size: 13px;\n}\n\n.splitCaret_427795137 {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n margin-left: 1px;\n padding: 6px 6px;\n}\n\n.menu_427795137 {\n position: absolute;\n right: 0;\n top: calc(100% + 4px);\n z-index: 40;\n width: max-content;\n min-width: 0;\n background: var(--dsw-alias-surface-raised, var(--dsw-alias-bg-layer-3));\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n box-shadow: var(--dsw-shadow-lv3);\n backdrop-filter: var(--dsw-menu-backdrop-filter);\n padding: 3px;\n display: flex;\n flex-direction: column;\n}\n\n.menuItem_427795137 {\n display: block;\n width: 100%;\n text-align: left;\n background: none;\n border: 0;\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 12px;\n line-height: 1.4;\n padding: 5px 10px;\n border-radius: 5px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.menuItem_427795137:hover:not([disabled]) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.menuItem_427795137[disabled] {\n opacity: 0.5;\n cursor: default;\n}\n\n.menuSub_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n margin-top: 1px;\n}\n\n/* Branch section — two IDEA-style trees: 本地 → branches, 远程 → per-remote → branches */\n\n.treeHead_427795137 {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 6px;\n border-radius: 6px;\n cursor: pointer;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-label-secondary);\n user-select: none;\n}\n\n.treeHead_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n.treeChevron_427795137 {\n display: inline-flex;\n align-items: center;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.treeChildren_427795137 {\n display: flex;\n flex-direction: column;\n margin-left: 12px;\n}\n\n.treeHeadNest_427795137 {\n font-weight: 400;\n}\n\n.treeNest_427795137 {\n margin-left: 12px;\n}\n\n.branchRow_427795137 {\n display: grid;\n grid-template-columns: 20px minmax(0, 1fr) auto;\n gap: 8px;\n align-items: center;\n padding: 5px 8px;\n border-radius: 7px;\n /* The action menu opens on RIGHT-CLICK (Enter/Space for keyboard) — an\n * arrow, not the pointing hand a left-click affordance would promise. */\n cursor: default;\n font-size: 12.5px;\n color: var(--dsw-alias-label-primary);\n}\n\n.branchRow_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n/* Whole-row highlight for the current branch: the ✓ alone read as too\n * quiet inside the tree. A translucent brand-blue wash plus the inset left\n * accent and a colored name survives both themes; #4c6bf5 is the same\n * hardcoded blue actPrimary uses (the brand token does not resolve\n * reliably inside the better-sidebar panel). */\n.branchRowCurrent_427795137 {\n background: rgba(76, 107, 245, 0.16);\n box-shadow: inset 2px 0 0 #4c6bf5;\n cursor: default;\n}\n\n.branchRowCurrent_427795137:hover {\n background: rgba(76, 107, 245, 0.16);\n}\n\n.branchRowCurrent_427795137 .branchName_427795137 {\n color: #4c6bf5;\n font-weight: 600;\n}\n\n.branchMark_427795137 {\n color: var(--dsw-alias-brand-primary);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n}\n\n.branchName_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 500;\n}\n\n.branchKind_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 10px;\n margin-left: 6px;\n}\n\n.branchTrack_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n flex: none;\n}\n\n/* The branch action menu is portaled; fixed positioning by row rect. */\n.floatMenu_427795137 {\n position: fixed;\n z-index: 60;\n min-width: 190px;\n}\n\n.menuHead_427795137 {\n padding: 6px 10px 5px;\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary);\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n margin-bottom: 3px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.menuHeadStrong_427795137 {\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n.menuDanger_427795137 {\n color: var(--dsw-alias-status-danger, #d5304f);\n}\n\n.formField_427795137 {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.formLabel_427795137 {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.formInput_427795137 {\n font: inherit;\n font-size: 13px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n padding: 7px 10px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n outline: none;\n}\n\n.formInput_427795137:focus {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.formError_427795137 {\n font-size: 12px;\n color: var(--dsw-alias-status-danger, #d5304f);\n min-height: 16px;\n}\n\n/* Commit history */\n\n.historyHead_427795137 {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n.searchBox_427795137 {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 6px;\n flex: 1;\n max-width: 300px;\n min-width: 140px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 7px;\n padding: 0 9px;\n height: 28px;\n color: var(--dsw-alias-label-tertiary);\n background: var(--dsw-alias-bg-layer-3);\n}\n\n/* The branch card's head variant: sits right of the title and grows toward\n * the tools instead of being right-aligned by `margin-left: auto`. The\n * overrides win because both selectors are single classes and this one is\n * declared later. */\n.searchBoxHead_427795137 {\n margin-left: 0;\n flex: 1 1 auto;\n max-width: 320px;\n min-width: 90px;\n}\n\n.searchInput_427795137 {\n flex: 1;\n min-width: 0;\n border: 0;\n background: none;\n outline: none;\n font: inherit;\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n}\n\n.searchInput_427795137::placeholder {\n color: var(--dsw-alias-label-tertiary);\n}\n\n.commitRow_427795137 {\n position: relative;\n display: grid;\n grid-template-columns: 22px minmax(0, 1fr) auto;\n gap: 8px;\n align-items: center;\n padding: 6px 8px 6px 2px;\n cursor: pointer;\n border-radius: 7px;\n}\n\n.commitRow_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.commitRowOpen_427795137 {\n background: var(--dsw-alias-bg-layer-3);\n}\n\n.commitRowOpen_427795137:hover {\n background: var(--dsw-alias-bg-layer-3);\n}\n\n.commitGraph_427795137 {\n position: relative;\n align-self: stretch;\n min-height: 34px;\n}\n\n.commitGraph_427795137::before {\n content: \"\";\n position: absolute;\n left: 10px;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--dsw-alias-border-l2);\n}\n\n.commitRowFirst_427795137 .commitGraph_427795137::before {\n top: 50%;\n}\n\n.commitRowLast_427795137 .commitGraph_427795137::before {\n bottom: 50%;\n}\n\n/* A small plain dot on the rail — the old 10px dot with a fat card-colored\n * ring read as a heavy black blob. #4c6bf5 for the same reason actPrimary\n * hardcodes it (the brand token does not resolve reliably in this panel). */\n.commitDot_427795137 {\n position: absolute;\n left: 8px;\n top: 50%;\n transform: translateY(-50%);\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: #4c6bf5;\n}\n\n.commitDotMerge_427795137 {\n background: #8350d8;\n}\n\n.commitMain_427795137 {\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 1px;\n}\n\n.commitSubject_427795137 {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n font-size: 12.5px;\n color: var(--dsw-alias-label-primary);\n}\n\n.commitSubjectText_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.commitRowOpen_427795137 .commitSubjectText_427795137 {\n font-weight: 600;\n}\n\n.refBadges_427795137 {\n display: inline-flex;\n gap: 4px;\n flex: none;\n}\n\n.refBadge_427795137 {\n font-size: 10px;\n line-height: 1;\n border-radius: 4px;\n padding: 3px 5px;\n font-weight: 500;\n white-space: nowrap;\n max-width: 140px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.refHead_427795137 {\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary-bluish);\n border: 0.5px solid var(--dsw-alias-border-l4);\n}\n\n.refTag_427795137 {\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-warning, #b07000);\n border: 0.5px solid var(--dsw-alias-border-l4);\n}\n\n.refRemote_427795137 {\n background: var(--dsw-alias-bg-layer-3);\n color: #8350d8;\n border: 0.5px solid var(--dsw-alias-border-l4);\n}\n\n.refLocal_427795137 {\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-status-success, #218358);\n border: 0.5px solid var(--dsw-alias-border-l4);\n}\n\n.commitMeta_427795137 {\n display: flex;\n align-items: center;\n gap: 6px;\n color: var(--dsw-alias-label-tertiary);\n font-size: 11.5px;\n}\n\n.commitSide_427795137 {\n display: flex;\n align-items: center;\n gap: 8px;\n color: var(--dsw-alias-label-tertiary);\n font-size: 11.5px;\n flex: none;\n}\n\n.commitHash_427795137 {\n font-family: ui-monospace, Consolas, monospace;\n}\n\n.commitExpand_427795137 {\n grid-column: 1 / -1;\n padding: 2px 4px 8px 28px;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.commitExpandTitle_427795137 {\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary);\n margin-bottom: 3px;\n}\n\n.fileRow_427795137 {\n display: grid;\n grid-template-columns: 18px minmax(0, 1fr) auto;\n gap: 8px;\n align-items: center;\n padding: 3px 6px;\n border-radius: 6px;\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n}\n\n.fileRow_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.filePath_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: ui-monospace, Consolas, monospace;\n font-size: 11.5px;\n direction: rtl;\n text-align: left;\n}\n\n.fileNums_427795137 {\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-tertiary);\n flex: none;\n}\n\n.fileAdd_427795137 { color: var(--dsw-alias-status-success, #218358); }\n.fileDel_427795137 { color: var(--dsw-alias-status-danger, #d5304f); }\n\n.empty_427795137 {\n padding: 28px 16px;\n text-align: center;\n color: var(--dsw-alias-label-tertiary);\n font-size: 12.5px;\n}\n\n.errorText_427795137 {\n padding: 20px 16px;\n text-align: center;\n color: var(--dsw-alias-status-danger, #d5304f);\n font-size: 12.5px;\n}\n\n.moreRow_427795137 {\n display: flex;\n justify-content: center;\n padding: 8px 0 2px;\n}\n";
3743
+ const css$2 = "/* Git panel (sidebar global panel). Three stacked sections over the main\n * column: uncommitted changes + commit box, branches, commit history. Host\n * tokens only; the few status colors fall back to the same literals the\n * settings card uses. */\n\n.page_427795137 {\n display: flex;\n flex-direction: column;\n min-height: 0;\n height: 100%;\n}\n\n.topbar_427795137 {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 16px 8px;\n}\n\n.back_427795137 {\n flex: none;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n border: 0;\n background: none;\n cursor: pointer;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n padding: 4px 8px;\n border-radius: 8px;\n}\n\n.back_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n.title_427795137 {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n.repoPill_427795137 {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n font-size: 12px;\n line-height: 1.4;\n color: var(--dsw-alias-label-secondary);\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 999px;\n padding: 3px 10px;\n white-space: nowrap;\n overflow: hidden;\n}\n\n.repoName_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.branchName_427795137 {\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n.track_427795137 {\n font-variant-numeric: tabular-nums;\n}\n\n.topbarActions_427795137 {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 6px;\n flex: none;\n}\n\n.refreshBtn_427795137 {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: 0;\n border-radius: 8px;\n background: none;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n}\n\n.refreshBtn_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n.refreshBtn_427795137[disabled] {\n opacity: 0.5;\n cursor: default;\n}\n\n/* Cn-accelerate: the topbar 国内加速 button and its anchored popover. The\n * button's on-state is brand tint + brand text + green dot; the popover\n * mirrors the settings card's switch/input affordances over this panel's\n * tokens (popup chrome follows .menu_427795137 above). */\n\n.cnWrap_427795137 {\n position: relative;\n display: inline-flex;\n}\n\n.cnBtn_427795137,\n.cnBtnOn_427795137 {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n height: 28px;\n padding: 0 10px;\n border: 0;\n border-radius: 8px;\n background: none;\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n font-size: 12px;\n line-height: 1;\n cursor: pointer;\n}\n\n.cnBtn_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n.cnBtnOn_427795137 {\n background: rgba(76, 107, 245, 0.16);\n color: var(--dsw-alias-brand-primary);\n}\n\n.cnBtnOn_427795137:hover {\n background: rgba(76, 107, 245, 0.22);\n color: var(--dsw-alias-brand-primary);\n}\n\n.cnDot_427795137 {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--dsw-alias-status-success, #218358);\n}\n\n.cnPop_427795137 {\n position: absolute;\n right: 0;\n top: calc(100% + 6px);\n z-index: 40;\n width: 320px;\n background: var(--dsw-alias-surface-raised, var(--dsw-alias-bg-layer-3));\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n box-shadow: var(--dsw-shadow-lv3);\n backdrop-filter: var(--dsw-menu-backdrop-filter);\n padding: 12px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.cnPopHead_427795137 {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n\n.cnPopTitle_427795137 {\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n.cnSwitchRow_427795137 {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n}\n\n.cnSwitch_427795137 {\n width: 16px;\n height: 16px;\n accent-color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n}\n\n.cnSwitch_427795137:disabled {\n cursor: default;\n}\n\n.cnHint_427795137 {\n margin: 0;\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.cnFieldHead_427795137 {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n font-size: 12px;\n}\n\n.cnFieldLabel_427795137 {\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n.cnLink_427795137 {\n border: 0;\n background: none;\n padding: 0;\n font: inherit;\n font-size: 12px;\n color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n}\n\n.cnLink_427795137:hover {\n text-decoration: underline;\n}\n\n.cnLink_427795137:disabled {\n opacity: 0.5;\n cursor: default;\n text-decoration: none;\n}\n\n.cnInput_427795137,\n.cnInputBad_427795137 {\n box-sizing: border-box;\n width: 100%;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 12px;\n padding: 6px 8px;\n outline: none;\n}\n\n.cnInput_427795137:focus {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.cnInputBad_427795137,\n.cnInputBad_427795137:focus {\n border-color: var(--dsw-alias-status-danger, #d5304f);\n}\n\n.cnError_427795137 {\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-status-danger, #d5304f);\n}\n\n.cnFooter_427795137 {\n display: flex;\n justify-content: flex-end;\n gap: 6px;\n margin-top: 2px;\n}\n\n.cnGhost_427795137,\n.cnSave_427795137 {\n border: 0;\n border-radius: 6px;\n font: inherit;\n font-size: 12px;\n padding: 5px 12px;\n cursor: pointer;\n}\n\n.cnGhost_427795137 {\n background: none;\n color: var(--dsw-alias-label-secondary);\n}\n\n.cnGhost_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n.cnSave_427795137 {\n background: var(--dsw-alias-brand-primary);\n color: #fff;\n}\n\n.cnSave_427795137:disabled {\n opacity: 0.5;\n cursor: default;\n}\n\n.scroll_427795137 {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n padding: 0 16px 16px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n/* The page column below the topbar is a definite-height flex column and the\n * three cards split it 2:1:2 (uncommitted : branches : history) — five\n * shares, the topbar and gaps taking their natural height first. Each body\n * scrolls INSIDE its share — min-height: 0 is what lets a flex child shrink\n * below its content (the old natural-height model painted overflowing rows\n * over the sections below). A floor keeps a very short panel from\n * collapsing a card to its bare head; past the floor the column itself\n * scrolls. */\n.section_427795137 {\n border: 0.5px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-2);\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n\n.sectionFill_427795137 {\n flex: 1 1 0;\n min-height: 84px;\n}\n\n/* The three flex-grow overrides of the 2:1:2 split. */\n.sectionChanges_427795137 {\n flex-grow: 2;\n}\n\n.sectionBranches_427795137 {\n flex-grow: 1;\n}\n\n.sectionHistory_427795137 {\n flex-grow: 2;\n}\n\n.sectionHead_427795137 {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n.sectionTitle_427795137 {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n.countBadge_427795137 {\n font-size: 10px;\n line-height: 1;\n border-radius: 999px;\n padding: 3px 7px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-secondary);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n.sectionTools_427795137 {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.sectionBody_427795137 {\n padding: 10px 12px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n flex: 1 1 auto;\n min-height: 0;\n}\n\n/* The branch and history bodies scroll as a whole inside their share. */\n.sectionBodyScroll_427795137 {\n overflow-y: auto;\n overscroll-behavior: contain;\n}\n\n/* Inside the uncommitted card only the file lists scroll; the commit box\n * below stays pinned however long the change tree is. */\n.changeList_427795137 {\n display: flex;\n flex-direction: column;\n gap: 8px;\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n overscroll-behavior: contain;\n}\n\n/* The 全部展开/全部折叠 text chips in the branch card head. */\n.treeToggle_427795137 {\n background: none;\n border: 0;\n padding: 2px 5px;\n font: inherit;\n font-size: 11.5px;\n line-height: 1.4;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n border-radius: 5px;\n white-space: nowrap;\n}\n\n.treeToggle_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-brand-primary);\n}\n\n/* Small action chip used across the section heads and the commit row. */\n.act_427795137 {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n font: inherit;\n font-size: 12px;\n line-height: 1.4;\n cursor: pointer;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 7px;\n padding: 4px 10px;\n white-space: nowrap;\n}\n\n.act_427795137:hover:not([disabled]) {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.act_427795137[disabled] {\n opacity: 0.45;\n cursor: default;\n}\n\n.actPrimary_427795137 {\n /* Hardcoded blue by request — the brand token does not resolve reliably\n * inside the better-sidebar panel. */\n background: #4c6bf5;\n border-color: transparent;\n color: #fff;\n}\n\n.actPrimary_427795137:hover:not([disabled]) {\n border-color: transparent;\n filter: brightness(1.06);\n}\n\n/* Uncommitted changes */\n\n.groupTitleRow_427795137 {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.linkBtn_427795137 {\n margin-left: auto;\n background: none;\n border: 0;\n padding: 2px 4px;\n font: inherit;\n font-size: 12px;\n color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n border-radius: 5px;\n}\n\n.linkBtn_427795137:hover:not([disabled]) {\n text-decoration: underline;\n}\n\n.linkBtn_427795137[disabled] {\n opacity: 0.5;\n cursor: default;\n}\n\n.changeRow_427795137 {\n display: grid;\n grid-template-columns: 16px 18px minmax(0, 1fr) auto;\n gap: 8px;\n align-items: center;\n padding: 3px 6px;\n border-radius: 6px;\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n}\n\n.changeRow_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.changeCheck_427795137 {\n width: 14px;\n height: 14px;\n accent-color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n margin: 0;\n}\n\n.statusBadge_427795137 {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n font-size: 10px;\n line-height: 1;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-weight: 700;\n color: #fff;\n box-sizing: border-box;\n}\n\n.statusA_427795137 { background: var(--dsw-alias-status-success, #218358); }\n.statusM_427795137 { background: var(--dsw-alias-label-warning, #b07000); }\n.statusD_427795137 { background: var(--dsw-alias-status-danger, #d5304f); }\n.statusR_427795137 { background: #8350d8; }\n.statusC_427795137 { background: #8350d8; }\n.statusT_427795137 { background: #5b7a9d; }\n.statusQ_427795137 { background: var(--dsw-alias-label-dimmed, #9aa0aa); }\n.statusU_427795137 { background: #d5502e; }\n\n.changePath_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: ui-monospace, Consolas, monospace;\n font-size: 11.5px;\n direction: rtl;\n text-align: left;\n}\n\n.stageHint_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n flex: none;\n}\n\n.commitBox_427795137 {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 4px;\n flex: none;\n}\n\n.commitInput_427795137 {\n width: 100%;\n box-sizing: border-box;\n resize: vertical;\n min-height: 52px;\n font: inherit;\n font-size: 12.5px;\n line-height: 1.5;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n padding: 7px 10px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n outline: none;\n}\n\n.commitInput_427795137:focus {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.commitActions_427795137 {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.commitHint_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n min-height: 16px;\n}\n\n.split_427795137 {\n margin-left: auto;\n display: flex;\n position: relative;\n}\n\n.splitMain_427795137 {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n padding: 6px 16px;\n font-size: 13px;\n}\n\n.splitCaret_427795137 {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n margin-left: 1px;\n padding: 6px 6px;\n}\n\n.menu_427795137 {\n position: absolute;\n right: 0;\n top: calc(100% + 4px);\n z-index: 40;\n width: max-content;\n min-width: 0;\n background: var(--dsw-alias-surface-raised, var(--dsw-alias-bg-layer-3));\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n box-shadow: var(--dsw-shadow-lv3);\n backdrop-filter: var(--dsw-menu-backdrop-filter);\n padding: 3px;\n display: flex;\n flex-direction: column;\n}\n\n.menuItem_427795137 {\n display: block;\n width: 100%;\n text-align: left;\n background: none;\n border: 0;\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 12px;\n line-height: 1.4;\n padding: 5px 10px;\n border-radius: 5px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.menuItem_427795137:hover:not([disabled]) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.menuItem_427795137[disabled] {\n opacity: 0.5;\n cursor: default;\n}\n\n.menuSub_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n margin-top: 1px;\n}\n\n/* Branch section — two IDEA-style trees: 本地 → branches, 远程 → per-remote → branches */\n\n.treeHead_427795137 {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 6px;\n border-radius: 6px;\n cursor: pointer;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-label-secondary);\n user-select: none;\n}\n\n.treeHead_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n.treeChevron_427795137 {\n display: inline-flex;\n align-items: center;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.treeChildren_427795137 {\n display: flex;\n flex-direction: column;\n margin-left: 12px;\n}\n\n.treeHeadNest_427795137 {\n font-weight: 400;\n}\n\n.treeNest_427795137 {\n margin-left: 12px;\n}\n\n.branchRow_427795137 {\n display: grid;\n grid-template-columns: 20px minmax(0, 1fr) auto;\n gap: 8px;\n align-items: center;\n padding: 5px 8px;\n border-radius: 7px;\n /* The action menu opens on RIGHT-CLICK (Enter/Space for keyboard) — an\n * arrow, not the pointing hand a left-click affordance would promise. */\n cursor: default;\n font-size: 12.5px;\n color: var(--dsw-alias-label-primary);\n}\n\n.branchRow_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n/* Whole-row highlight for the current branch: the ✓ alone read as too\n * quiet inside the tree. A translucent brand-blue wash plus the inset left\n * accent and a colored name survives both themes; #4c6bf5 is the same\n * hardcoded blue actPrimary uses (the brand token does not resolve\n * reliably inside the better-sidebar panel). */\n.branchRowCurrent_427795137 {\n background: rgba(76, 107, 245, 0.16);\n box-shadow: inset 2px 0 0 #4c6bf5;\n cursor: default;\n}\n\n.branchRowCurrent_427795137:hover {\n background: rgba(76, 107, 245, 0.16);\n}\n\n.branchRowCurrent_427795137 .branchName_427795137 {\n color: #4c6bf5;\n font-weight: 600;\n}\n\n.branchMark_427795137 {\n color: var(--dsw-alias-brand-primary);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n}\n\n.branchName_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 500;\n}\n\n.branchKind_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 10px;\n margin-left: 6px;\n}\n\n.branchTrack_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n flex: none;\n}\n\n/* The branch action menu is portaled; fixed positioning by row rect. */\n.floatMenu_427795137 {\n position: fixed;\n z-index: 60;\n min-width: 190px;\n}\n\n.menuHead_427795137 {\n padding: 6px 10px 5px;\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary);\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n margin-bottom: 3px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.menuHeadStrong_427795137 {\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n.menuDanger_427795137 {\n color: var(--dsw-alias-status-danger, #d5304f);\n}\n\n.formField_427795137 {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.formLabel_427795137 {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.formInput_427795137 {\n font: inherit;\n font-size: 13px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n padding: 7px 10px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n outline: none;\n}\n\n.formInput_427795137:focus {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.formError_427795137 {\n font-size: 12px;\n color: var(--dsw-alias-status-danger, #d5304f);\n min-height: 16px;\n}\n\n/* Commit history */\n\n.historyHead_427795137 {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n.searchBox_427795137 {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 6px;\n flex: 1;\n max-width: 300px;\n min-width: 140px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 7px;\n padding: 0 9px;\n height: 28px;\n color: var(--dsw-alias-label-tertiary);\n background: var(--dsw-alias-bg-layer-3);\n}\n\n/* The branch card's head variant: sits right of the title and grows toward\n * the tools instead of being right-aligned by `margin-left: auto`. The\n * overrides win because both selectors are single classes and this one is\n * declared later. */\n.searchBoxHead_427795137 {\n margin-left: 0;\n flex: 1 1 auto;\n max-width: 320px;\n min-width: 90px;\n}\n\n.searchInput_427795137 {\n flex: 1;\n min-width: 0;\n border: 0;\n background: none;\n outline: none;\n font: inherit;\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n}\n\n.searchInput_427795137::placeholder {\n color: var(--dsw-alias-label-tertiary);\n}\n\n.commitRow_427795137 {\n position: relative;\n display: grid;\n grid-template-columns: 22px minmax(0, 1fr) auto;\n gap: 8px;\n align-items: center;\n padding: 6px 8px 6px 2px;\n cursor: pointer;\n border-radius: 7px;\n}\n\n.commitRow_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.commitRowOpen_427795137 {\n background: var(--dsw-alias-bg-layer-3);\n}\n\n.commitRowOpen_427795137:hover {\n background: var(--dsw-alias-bg-layer-3);\n}\n\n.commitGraph_427795137 {\n position: relative;\n align-self: stretch;\n min-height: 34px;\n}\n\n.commitGraph_427795137::before {\n content: \"\";\n position: absolute;\n left: 10px;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--dsw-alias-border-l2);\n}\n\n.commitRowFirst_427795137 .commitGraph_427795137::before {\n top: 50%;\n}\n\n.commitRowLast_427795137 .commitGraph_427795137::before {\n bottom: 50%;\n}\n\n/* A small plain dot on the rail — the old 10px dot with a fat card-colored\n * ring read as a heavy black blob. #4c6bf5 for the same reason actPrimary\n * hardcodes it (the brand token does not resolve reliably in this panel). */\n.commitDot_427795137 {\n position: absolute;\n left: 8px;\n top: 50%;\n transform: translateY(-50%);\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: #4c6bf5;\n}\n\n.commitDotMerge_427795137 {\n background: #8350d8;\n}\n\n.commitMain_427795137 {\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 1px;\n}\n\n.commitSubject_427795137 {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n font-size: 12.5px;\n color: var(--dsw-alias-label-primary);\n}\n\n.commitSubjectText_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.commitRowOpen_427795137 .commitSubjectText_427795137 {\n font-weight: 600;\n}\n\n.refBadges_427795137 {\n display: inline-flex;\n gap: 4px;\n flex: none;\n}\n\n.refBadge_427795137 {\n font-size: 10px;\n line-height: 1;\n border-radius: 4px;\n padding: 3px 5px;\n font-weight: 500;\n white-space: nowrap;\n max-width: 140px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.refHead_427795137 {\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary-bluish);\n border: 0.5px solid var(--dsw-alias-border-l4);\n}\n\n.refTag_427795137 {\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-warning, #b07000);\n border: 0.5px solid var(--dsw-alias-border-l4);\n}\n\n.refRemote_427795137 {\n background: var(--dsw-alias-bg-layer-3);\n color: #8350d8;\n border: 0.5px solid var(--dsw-alias-border-l4);\n}\n\n.refLocal_427795137 {\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-status-success, #218358);\n border: 0.5px solid var(--dsw-alias-border-l4);\n}\n\n.commitMeta_427795137 {\n display: flex;\n align-items: center;\n gap: 6px;\n color: var(--dsw-alias-label-tertiary);\n font-size: 11.5px;\n}\n\n.commitSide_427795137 {\n display: flex;\n align-items: center;\n gap: 8px;\n color: var(--dsw-alias-label-tertiary);\n font-size: 11.5px;\n flex: none;\n}\n\n.commitHash_427795137 {\n font-family: ui-monospace, Consolas, monospace;\n}\n\n.commitExpand_427795137 {\n grid-column: 1 / -1;\n padding: 2px 4px 8px 28px;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.commitExpandTitle_427795137 {\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary);\n margin-bottom: 3px;\n}\n\n.fileRow_427795137 {\n display: grid;\n grid-template-columns: 18px minmax(0, 1fr) auto;\n gap: 8px;\n align-items: center;\n padding: 3px 6px;\n border-radius: 6px;\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n}\n\n.fileRow_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.filePath_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: ui-monospace, Consolas, monospace;\n font-size: 11.5px;\n direction: rtl;\n text-align: left;\n}\n\n.fileNums_427795137 {\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-tertiary);\n flex: none;\n}\n\n.fileAdd_427795137 { color: var(--dsw-alias-status-success, #218358); }\n.fileDel_427795137 { color: var(--dsw-alias-status-danger, #d5304f); }\n\n.empty_427795137 {\n padding: 28px 16px;\n text-align: center;\n color: var(--dsw-alias-label-tertiary);\n font-size: 12.5px;\n}\n\n.errorText_427795137 {\n padding: 20px 16px;\n text-align: center;\n color: var(--dsw-alias-status-danger, #d5304f);\n font-size: 12.5px;\n}\n\n.moreRow_427795137 {\n display: flex;\n justify-content: center;\n padding: 8px 0 2px;\n}\n";
3569
3744
  const tagId$2 = "dsh-git-branch-manage/GitHistoryPanel.module.css";
3570
3745
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=\"" + tagId$2 + "\"]") === null) {
3571
3746
  const tag = document.createElement("style");
@@ -3585,6 +3760,26 @@ window.__ModuleLoader__.load({
3585
3760
  "track": "track_427795137",
3586
3761
  "topbarActions": "topbarActions_427795137",
3587
3762
  "refreshBtn": "refreshBtn_427795137",
3763
+ "menu": "menu_427795137",
3764
+ "cnWrap": "cnWrap_427795137",
3765
+ "cnBtn": "cnBtn_427795137",
3766
+ "cnBtnOn": "cnBtnOn_427795137",
3767
+ "cnDot": "cnDot_427795137",
3768
+ "cnPop": "cnPop_427795137",
3769
+ "cnPopHead": "cnPopHead_427795137",
3770
+ "cnPopTitle": "cnPopTitle_427795137",
3771
+ "cnSwitchRow": "cnSwitchRow_427795137",
3772
+ "cnSwitch": "cnSwitch_427795137",
3773
+ "cnHint": "cnHint_427795137",
3774
+ "cnFieldHead": "cnFieldHead_427795137",
3775
+ "cnFieldLabel": "cnFieldLabel_427795137",
3776
+ "cnLink": "cnLink_427795137",
3777
+ "cnInput": "cnInput_427795137",
3778
+ "cnInputBad": "cnInputBad_427795137",
3779
+ "cnError": "cnError_427795137",
3780
+ "cnFooter": "cnFooter_427795137",
3781
+ "cnGhost": "cnGhost_427795137",
3782
+ "cnSave": "cnSave_427795137",
3588
3783
  "scroll": "scroll_427795137",
3589
3784
  "section": "section_427795137",
3590
3785
  "sectionFill": "sectionFill_427795137",
@@ -3623,7 +3818,6 @@ window.__ModuleLoader__.load({
3623
3818
  "split": "split_427795137",
3624
3819
  "splitMain": "splitMain_427795137",
3625
3820
  "splitCaret": "splitCaret_427795137",
3626
- "menu": "menu_427795137",
3627
3821
  "menuItem": "menuItem_427795137",
3628
3822
  "menuSub": "menuSub_427795137",
3629
3823
  "treeHead": "treeHead_427795137",
@@ -4116,6 +4310,135 @@ window.__ModuleLoader__.load({
4116
4310
  });
4117
4311
  }
4118
4312
  //#endregion
4313
+ //#region src/client/panel/CnAcceleratePopover.tsx
4314
+ /**
4315
+ * CnAcceleratePopover: the 国内加速 popover under the topbar button.
4316
+ *
4317
+ * This is the SHORTCUT surface of the same staged form the settings card row
4318
+ * edits ({@link ../accel-form}): enable switch + mirror URL stage together
4319
+ * and commit together on 保存. Closing the popover — Esc, outside click, 取消
4320
+ * — discards the staged edits through the owner's close handler, so an
4321
+ * abandoned draft can never land half-written.
4322
+ *
4323
+ * Layout follows docs/design/cn-accelerate.md §2.2: title + switch → security
4324
+ * hint → URL field with 恢复默认 → error line → footer.
4325
+ *
4326
+ * @module git-worktree/client/panel/CnAcceleratePopover
4327
+ */
4328
+ function CnAcceleratePopover(props) {
4329
+ const { accel, t, pushToast, onClose } = props;
4330
+ const rootRef = (0, react.useRef)(null);
4331
+ const state = (0, react.useSyncExternalStore)(accel.store.subscribe, accel.store.getSnapshot);
4332
+ (0, react.useEffect)(() => {
4333
+ const onKeyDown = (event) => {
4334
+ if (event.key === "Escape") onClose();
4335
+ };
4336
+ const onPointerDown = (event) => {
4337
+ if (rootRef.current !== null && !rootRef.current.contains(event.target)) onClose();
4338
+ };
4339
+ document.addEventListener("keydown", onKeyDown);
4340
+ document.addEventListener("pointerdown", onPointerDown);
4341
+ return () => {
4342
+ document.removeEventListener("keydown", onKeyDown);
4343
+ document.removeEventListener("pointerdown", onPointerDown);
4344
+ };
4345
+ }, [onClose]);
4346
+ const locked = state.saving || !state.writable;
4347
+ const onSave = (0, react.useCallback)(() => {
4348
+ if (state.saving || state.urlError !== null || !state.writable) return;
4349
+ (async () => {
4350
+ await accel.saveAccel();
4351
+ const after = accel.store.getSnapshot();
4352
+ if (after.failed) {
4353
+ pushToast(t("accel.saveFailed"), "error");
4354
+ return;
4355
+ }
4356
+ pushToast(after.enabled ? t("accel.savedOn", { url: after.urlText }) : t("accel.savedOff"), "success");
4357
+ onClose();
4358
+ })();
4359
+ }, [
4360
+ accel,
4361
+ onClose,
4362
+ pushToast,
4363
+ state.saving,
4364
+ state.urlError,
4365
+ state.writable,
4366
+ t
4367
+ ]);
4368
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4369
+ className: GitHistoryPanel_module_default.cnPop,
4370
+ ref: rootRef,
4371
+ role: "dialog",
4372
+ "aria-label": t("panel.cnAccelerate"),
4373
+ children: [
4374
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4375
+ className: GitHistoryPanel_module_default.cnPopHead,
4376
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4377
+ className: GitHistoryPanel_module_default.cnPopTitle,
4378
+ children: t("panel.cnAccelerate")
4379
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
4380
+ className: GitHistoryPanel_module_default.cnSwitchRow,
4381
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("accel.enable") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4382
+ type: "checkbox",
4383
+ className: GitHistoryPanel_module_default.cnSwitch,
4384
+ checked: state.enabled,
4385
+ disabled: locked,
4386
+ onChange: (event) => accel.setAccelEnabled(event.target.checked)
4387
+ })]
4388
+ })]
4389
+ }),
4390
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4391
+ className: GitHistoryPanel_module_default.cnHint,
4392
+ children: t("accel.hint")
4393
+ }),
4394
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4395
+ className: GitHistoryPanel_module_default.cnFieldHead,
4396
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4397
+ className: GitHistoryPanel_module_default.cnFieldLabel,
4398
+ children: t("accel.urlLabel")
4399
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4400
+ type: "button",
4401
+ className: GitHistoryPanel_module_default.cnLink,
4402
+ disabled: locked,
4403
+ onClick: () => accel.restoreAccelDefault(),
4404
+ children: t("accel.restoreDefault")
4405
+ })]
4406
+ }),
4407
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4408
+ type: "text",
4409
+ className: state.urlError !== null ? GitHistoryPanel_module_default.cnInputBad : GitHistoryPanel_module_default.cnInput,
4410
+ value: state.urlText,
4411
+ spellCheck: false,
4412
+ disabled: locked,
4413
+ "aria-invalid": state.urlError !== null,
4414
+ onChange: (event) => accel.editAccelUrl(event.target.value),
4415
+ onKeyDown: (event) => {
4416
+ if (event.key === "Enter") onSave();
4417
+ }
4418
+ }),
4419
+ state.urlError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4420
+ className: GitHistoryPanel_module_default.cnError,
4421
+ children: state.urlError === "empty" ? t("accel.urlEmpty") : t("accel.urlInvalid")
4422
+ }) : null,
4423
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4424
+ className: GitHistoryPanel_module_default.cnFooter,
4425
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4426
+ type: "button",
4427
+ className: GitHistoryPanel_module_default.cnGhost,
4428
+ onClick: onClose,
4429
+ children: t("cancel")
4430
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4431
+ type: "button",
4432
+ className: GitHistoryPanel_module_default.cnSave,
4433
+ disabled: state.saving || state.urlError !== null || !state.writable,
4434
+ onClick: onSave,
4435
+ children: t("cardSave")
4436
+ })]
4437
+ })
4438
+ ]
4439
+ });
4440
+ }
4441
+ //#endregion
4119
4442
  //#region src/client/panel/HistorySection.tsx
4120
4443
  /**
4121
4444
  * HistorySection: the panel's third block — the commit list. One row shows
@@ -4656,7 +4979,7 @@ window.__ModuleLoader__.load({
4656
4979
  /** Data owner for the three sections. Remounts when `cwd` changes (the page
4657
4980
  * keys the mount by cwd), so no state crosses a repository boundary. */
4658
4981
  const GitHistoryPanelData = (0, react.forwardRef)(function GitHistoryPanelData(props, ref) {
4659
- const { cwd, model, t, pushToast } = props;
4982
+ const { cwd, model, t, pushToast, accelOn } = props;
4660
4983
  const [status, setStatus] = (0, react.useState)(null);
4661
4984
  const [statusError, setStatusError] = (0, react.useState)();
4662
4985
  const [changes, setChanges] = (0, react.useState)([]);
@@ -4850,12 +5173,19 @@ window.__ModuleLoader__.load({
4850
5173
  pushToast,
4851
5174
  t
4852
5175
  ]);
5176
+ /** Fetch/update failure toast text: the git failure line, plus the
5177
+ * mirror-unavailable hint while cn-accelerate is on (push is never
5178
+ * accelerated, so its failures stay hint-free). */
5179
+ const syncFailureText = (0, react.useCallback)((message) => {
5180
+ const generic = t("errorGeneric", { message });
5181
+ return accelOn() ? `${generic} ${t("accel.mirrorDownHint")}` : generic;
5182
+ }, [accelOn, t]);
4853
5183
  const onFetch = (0, react.useCallback)(() => {
4854
5184
  setSyncBusy(true);
4855
5185
  (async () => {
4856
5186
  try {
4857
5187
  const result = await requestFetch(cwd);
4858
- if (!result.ok) pushToast(t("errorGeneric", { message: result.error }), "error");
5188
+ if (!result.ok) pushToast(syncFailureText(result.error), "error");
4859
5189
  else {
4860
5190
  pushToast(t("fetchDone"), "success");
4861
5191
  emitGitMutated();
@@ -4869,6 +5199,7 @@ window.__ModuleLoader__.load({
4869
5199
  cwd,
4870
5200
  loadAll,
4871
5201
  pushToast,
5202
+ syncFailureText,
4872
5203
  t
4873
5204
  ]);
4874
5205
  const onUpdate = (0, react.useCallback)(() => {
@@ -4876,7 +5207,7 @@ window.__ModuleLoader__.load({
4876
5207
  (async () => {
4877
5208
  try {
4878
5209
  const result = await requestUpdate(cwd);
4879
- if (!result.ok) pushToast(t("errorGeneric", { message: result.error }), "error");
5210
+ if (!result.ok) pushToast(syncFailureText(result.error), "error");
4880
5211
  else {
4881
5212
  pushToast(result.updated ? t("updateDone", { branch: result.branch }) : t("updateUpToDate"), "success");
4882
5213
  emitGitMutated();
@@ -4890,6 +5221,7 @@ window.__ModuleLoader__.load({
4890
5221
  cwd,
4891
5222
  loadAll,
4892
5223
  pushToast,
5224
+ syncFailureText,
4893
5225
  t
4894
5226
  ]);
4895
5227
  const onPush = (0, react.useCallback)(() => {
@@ -5098,7 +5430,7 @@ window.__ModuleLoader__.load({
5098
5430
  /** The registered better-sidebar tab's content: chrome + data owner, keyed
5099
5431
  * by cwd so a session switch starts every section from scratch. */
5100
5432
  function GitPanelTab(props) {
5101
- const { ctx, scope, t } = props;
5433
+ const { ctx, scope, t, accel } = props;
5102
5434
  const subscribe = (0, react.useCallback)((listener) => ctx.sessions.list.subscribe(listener), [ctx]);
5103
5435
  const cwd = (0, react.useSyncExternalStore)(subscribe, () => scope.cwd ?? ctx.sessions.list.getSnapshot().byId[scope.sessionId]?.cwd);
5104
5436
  const model = (0, react.useSyncExternalStore)(subscribe, () => {
@@ -5117,6 +5449,13 @@ window.__ModuleLoader__.load({
5117
5449
  tone
5118
5450
  }]);
5119
5451
  }, []);
5452
+ const accelState = (0, react.useSyncExternalStore)(accel.store.subscribe, accel.store.getSnapshot);
5453
+ const [accelOpen, setAccelOpen] = (0, react.useState)(false);
5454
+ const accelOn = (0, react.useCallback)(() => accel.store.getSnapshot().enabled, [accel]);
5455
+ const closeAccel = (0, react.useCallback)(() => {
5456
+ accel.discardAccel();
5457
+ setAccelOpen(false);
5458
+ }, [accel]);
5120
5459
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5121
5460
  className: GitHistoryPanel_module_default.page,
5122
5461
  children: [
@@ -5128,9 +5467,35 @@ window.__ModuleLoader__.load({
5128
5467
  className: GitHistoryPanel_module_default.repoName,
5129
5468
  children: cwd
5130
5469
  })
5131
- }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5470
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5132
5471
  className: GitHistoryPanel_module_default.topbarActions,
5133
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5472
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5473
+ className: GitHistoryPanel_module_default.cnWrap,
5474
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5475
+ type: "button",
5476
+ className: accelState.enabled ? GitHistoryPanel_module_default.cnBtnOn : GitHistoryPanel_module_default.cnBtn,
5477
+ title: t("panel.cnAccelerate"),
5478
+ "aria-label": t("panel.cnAccelerate"),
5479
+ "aria-expanded": accelOpen,
5480
+ onClick: () => accelOpen ? closeAccel() : setAccelOpen(true),
5481
+ children: [
5482
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5483
+ "aria-hidden": "true",
5484
+ children: "⚡"
5485
+ }),
5486
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("panel.cnAccelerate") }),
5487
+ accelState.enabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5488
+ className: GitHistoryPanel_module_default.cnDot,
5489
+ "aria-hidden": "true"
5490
+ }) : null
5491
+ ]
5492
+ }), accelOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CnAcceleratePopover, {
5493
+ accel,
5494
+ t,
5495
+ pushToast,
5496
+ onClose: closeAccel
5497
+ }) : null]
5498
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5134
5499
  type: "button",
5135
5500
  className: GitHistoryPanel_module_default.refreshBtn,
5136
5501
  disabled: cwd === void 0,
@@ -5141,7 +5506,7 @@ window.__ModuleLoader__.load({
5141
5506
  size: 14,
5142
5507
  "aria-hidden": "true"
5143
5508
  })
5144
- })
5509
+ })]
5145
5510
  })]
5146
5511
  }),
5147
5512
  cwd === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -5152,7 +5517,8 @@ window.__ModuleLoader__.load({
5152
5517
  cwd,
5153
5518
  model,
5154
5519
  t,
5155
- pushToast
5520
+ pushToast,
5521
+ accelOn
5156
5522
  }, cwd),
5157
5523
  toasts.map((toast) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
5158
5524
  text: toast.text,
@@ -5760,7 +6126,7 @@ window.__ModuleLoader__.load({
5760
6126
  }
5761
6127
  //#endregion
5762
6128
  //#region \0git-worktree-css:/home/runner/work/dsh-git-branch/dsh-git-branch/src/client/GitWorktreeCard.module.css?inline
5763
- const css = "/* git-worktree bundle configuration on the Plugins page. The page owns the\n * chrome (title, icon, breadcrumb); this component renders either the\n * bundle-row summary line or the configuration form inside the page. Visual\n * language mirrors the settings section's own card chrome (layer fills,\n * footer buttons); tokens only, no literal colors. */\n\n/* The one-line preview on the bundle's row. */\n.summary_1281848784 {\n margin: 0;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* The configuration form inside the bundle's page. */\n.page_1281848784 {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.note_1281848784 {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.field_1281848784 {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.fieldLabel_1281848784 {\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* The read-only legacy storage root: text (not an input — the location is\n * historical and cannot be edited) plus its \"unused\" state on one row. */\n.rootRow_1281848784 {\n display: flex;\n align-items: baseline;\n gap: 8px;\n min-width: 0;\n}\n\n.rootValue_1281848784 {\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n overflow-wrap: anywhere;\n}\n\n.rootUnused_1281848784 {\n flex: none;\n font-size: 11px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 4px;\n padding: 0 5px;\n}\n\n.hint_1281848784 {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.footer_1281848784 {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n padding: 8px 0 4px;\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n.failed_1281848784 {\n flex: 1;\n min-width: 0;\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-error);\n}\n\n.discard_1281848784,\n.save_1281848784 {\n appearance: none;\n border: 0.5px solid transparent;\n border-radius: 8px;\n padding: 5px 14px;\n font: inherit;\n font-size: 13px;\n line-height: 1.5;\n cursor: pointer;\n}\n\n.discard_1281848784 {\n border-color: var(--dsw-alias-border-l2);\n background: none;\n color: var(--dsw-alias-label-secondary);\n}\n\n.discard_1281848784:hover:not(:disabled) {\n color: var(--dsw-alias-label-primary);\n border-color: var(--dsw-alias-label-dimmed);\n}\n\n.save_1281848784 {\n background: var(--dsw-alias-label-primary);\n color: var(--dsw-alias-bg-layer-3);\n}\n\n.discard_1281848784:disabled,\n.save_1281848784:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n.discard_1281848784:focus-visible,\n.save_1281848784:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* Sidebar-grouping switch: title + hint + caution beside a checkbox. */\n.toggleRow_1281848784 {\n margin-top: 10px;\n flex-direction: row;\n align-items: flex-start;\n gap: 10px;\n}\n\n.toggleText_1281848784 {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.toggleLabel_1281848784 {\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n.toggleMark_1281848784 {\n margin-left: 4px;\n font-weight: 400;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.toggleHint_1281848784 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n line-height: 18px;\n}\n\n.toggleControl_1281848784 {\n display: flex;\n align-items: center;\n gap: 6px;\n flex: none;\n margin-top: 2px;\n}\n\n.spinner_1281848784 {\n display: flex;\n color: var(--dsw-alias-label-tertiary);\n animation: spin 0.7s linear infinite;\n}\n\n@keyframes spin {\n to { transform: rotate(360deg); }\n}\n\n.toggle_1281848784 {\n width: 16px;\n height: 16px;\n accent-color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n}\n\n.toggle_1281848784:disabled {\n cursor: progress;\n}\n\n/* Manager entry row: title + hint on the left (the toggle rows' text\n * column), the launcher button on the right — the same posture as the\n * grouping switch, minus the checkbox. (.field_1281848784 is columnar; the row\n * direction is restated here.) */\n.manageRow_1281848784 {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n}\n\n.manage_1281848784 {\n appearance: none;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n background: none;\n font: inherit;\n font-size: 13px;\n color: var(--dsw-alias-label-primary);\n padding: 6px 12px;\n cursor: pointer;\n}\n\n.manage_1281848784:hover {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.manage_1281848784:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n/* Keep-cap row: the number input rides the toggle row's title column, and\n * stays disabled while the auto-prune switch is off (the cap means nothing\n * without the prune that enforces it). */\n.keepRow_1281848784 {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.keepControl_1281848784 {\n display: flex;\n justify-content: flex-start;\n}\n\n.keepInput_1281848784 {\n width: 96px;\n appearance: none;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-2);\n font: inherit;\n font-size: 13px;\n color: var(--dsw-alias-label-primary);\n padding: 6px 10px;\n}\n\n.keepInput_1281848784:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n.keepInput_1281848784:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n\n.keepBad_1281848784 {\n font-size: 12px;\n color: var(--dsw-alias-label-danger, #d64545);\n}\n\n.copyFilesRow_1281848784 {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.copyFilesInput_1281848784 {\n width: 100%;\n box-sizing: border-box;\n appearance: none;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-2);\n font: inherit;\n font-family: monospace;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n padding: 6px 10px;\n resize: vertical;\n min-height: 64px;\n}\n\n.copyFilesInput_1281848784:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n.copyFilesInput_1281848784:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n\n/* Write-through switches flip optimistically, so a rejected write shows up\n * as the control snapping back. This note sits in the switch's own text\n * column and says which one did not take. */\n.switchBad_1281848784 {\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-danger, #d64545);\n}\n\n/* The auto-prune audit trail (清理记录): newest first, one run per line —\n * relative time, the removed directories with their belonging, then the\n * run's exceptions in the secondary tone. */\n.historyRow_1281848784 {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.history_1281848784 {\n margin: 0;\n padding: 0;\n list-style: none;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.historyRun_1281848784 {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 4px 8px;\n border: 0.5px solid color-mix(in srgb, currentColor 12%, transparent);\n border-radius: 6px;\n}\n\n.historyTime_1281848784 {\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n.historyLine_1281848784 {\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-secondary);\n overflow-wrap: anywhere;\n}\n\n/* The fold toggle. A link, not a button chrome: it reveals rows in place,\n * it does not act on anything. */\n.historyMore_1281848784 {\n align-self: flex-start;\n padding: 0;\n border: none;\n background: none;\n font: inherit;\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-link, var(--dsw-alias-label-secondary));\n cursor: pointer;\n}\n\n.historyMore_1281848784:hover {\n text-decoration: underline;\n}\n";
6129
+ const css = "/* git-worktree bundle configuration on the Plugins page. The page owns the\n * chrome (title, icon, breadcrumb); this component renders either the\n * bundle-row summary line or the configuration form inside the page. Visual\n * language mirrors the settings section's own card chrome (layer fills,\n * footer buttons); tokens only, no literal colors. */\n\n/* The one-line preview on the bundle's row. */\n.summary_1281848784 {\n margin: 0;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n\n/* The configuration form inside the bundle's page. */\n.page_1281848784 {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.note_1281848784 {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.field_1281848784 {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.fieldLabel_1281848784 {\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* The read-only legacy storage root: text (not an input — the location is\n * historical and cannot be edited) plus its \"unused\" state on one row. */\n.rootRow_1281848784 {\n display: flex;\n align-items: baseline;\n gap: 8px;\n min-width: 0;\n}\n\n.rootValue_1281848784 {\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n overflow-wrap: anywhere;\n}\n\n.rootUnused_1281848784 {\n flex: none;\n font-size: 11px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 4px;\n padding: 0 5px;\n}\n\n.hint_1281848784 {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.footer_1281848784 {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n padding: 8px 0 4px;\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n.failed_1281848784 {\n flex: 1;\n min-width: 0;\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-error);\n}\n\n.discard_1281848784,\n.save_1281848784 {\n appearance: none;\n border: 0.5px solid transparent;\n border-radius: 8px;\n padding: 5px 14px;\n font: inherit;\n font-size: 13px;\n line-height: 1.5;\n cursor: pointer;\n}\n\n.discard_1281848784 {\n border-color: var(--dsw-alias-border-l2);\n background: none;\n color: var(--dsw-alias-label-secondary);\n}\n\n.discard_1281848784:hover:not(:disabled) {\n color: var(--dsw-alias-label-primary);\n border-color: var(--dsw-alias-label-dimmed);\n}\n\n.save_1281848784 {\n background: var(--dsw-alias-label-primary);\n color: var(--dsw-alias-bg-layer-3);\n}\n\n.discard_1281848784:disabled,\n.save_1281848784:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n.discard_1281848784:focus-visible,\n.save_1281848784:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* Sidebar-grouping switch: title + hint + caution beside a checkbox. */\n.toggleRow_1281848784 {\n margin-top: 10px;\n flex-direction: row;\n align-items: flex-start;\n gap: 10px;\n}\n\n.toggleText_1281848784 {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.toggleLabel_1281848784 {\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n.toggleMark_1281848784 {\n margin-left: 4px;\n font-weight: 400;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.toggleHint_1281848784 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n line-height: 18px;\n}\n\n.toggleControl_1281848784 {\n display: flex;\n align-items: center;\n gap: 6px;\n flex: none;\n margin-top: 2px;\n}\n\n.spinner_1281848784 {\n display: flex;\n color: var(--dsw-alias-label-tertiary);\n animation: spin 0.7s linear infinite;\n}\n\n@keyframes spin {\n to { transform: rotate(360deg); }\n}\n\n.toggle_1281848784 {\n width: 16px;\n height: 16px;\n accent-color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n}\n\n.toggle_1281848784:disabled {\n cursor: progress;\n}\n\n/* Manager entry row: title + hint on the left (the toggle rows' text\n * column), the launcher button on the right — the same posture as the\n * grouping switch, minus the checkbox. (.field_1281848784 is columnar; the row\n * direction is restated here.) */\n.manageRow_1281848784 {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n}\n\n.manage_1281848784 {\n appearance: none;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n background: none;\n font: inherit;\n font-size: 13px;\n color: var(--dsw-alias-label-primary);\n padding: 6px 12px;\n cursor: pointer;\n}\n\n.manage_1281848784:hover {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.manage_1281848784:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n/* Keep-cap row: the number input rides the toggle row's title column, and\n * stays disabled while the auto-prune switch is off (the cap means nothing\n * without the prune that enforces it). */\n.keepRow_1281848784 {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.keepControl_1281848784 {\n display: flex;\n justify-content: flex-start;\n}\n\n.keepInput_1281848784 {\n width: 96px;\n appearance: none;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-2);\n font: inherit;\n font-size: 13px;\n color: var(--dsw-alias-label-primary);\n padding: 6px 10px;\n}\n\n.keepInput_1281848784:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n.keepInput_1281848784:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n\n.keepBad_1281848784 {\n font-size: 12px;\n color: var(--dsw-alias-label-danger, #d64545);\n}\n\n.copyFilesRow_1281848784 {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.copyFilesInput_1281848784 {\n width: 100%;\n box-sizing: border-box;\n appearance: none;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-2);\n font: inherit;\n font-family: monospace;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n padding: 6px 10px;\n resize: vertical;\n min-height: 64px;\n}\n\n.copyFilesInput_1281848784:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n.copyFilesInput_1281848784:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n\n/* Cn-accelerate URL row: a single-line URL text field (the keep row's input\n * posture at full width). The invalid-draft line reuses .keepBad_1281848784. */\n.accelInput_1281848784 {\n width: 100%;\n box-sizing: border-box;\n appearance: none;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-2);\n font: inherit;\n font-family: monospace;\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n padding: 6px 10px;\n}\n\n.accelInput_1281848784:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n.accelInput_1281848784:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n\n/* Write-through switches flip optimistically, so a rejected write shows up\n * as the control snapping back. This note sits in the switch's own text\n * column and says which one did not take. */\n.switchBad_1281848784 {\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-danger, #d64545);\n}\n\n/* The auto-prune audit trail (清理记录): newest first, one run per line —\n * relative time, the removed directories with their belonging, then the\n * run's exceptions in the secondary tone. */\n.historyRow_1281848784 {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.history_1281848784 {\n margin: 0;\n padding: 0;\n list-style: none;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.historyRun_1281848784 {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: 4px 8px;\n border: 0.5px solid color-mix(in srgb, currentColor 12%, transparent);\n border-radius: 6px;\n}\n\n.historyTime_1281848784 {\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n.historyLine_1281848784 {\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-secondary);\n overflow-wrap: anywhere;\n}\n\n/* The fold toggle. A link, not a button chrome: it reveals rows in place,\n * it does not act on anything. */\n.historyMore_1281848784 {\n align-self: flex-start;\n padding: 0;\n border: none;\n background: none;\n font: inherit;\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-link, var(--dsw-alias-label-secondary));\n cursor: pointer;\n}\n\n.historyMore_1281848784:hover {\n text-decoration: underline;\n}\n";
5764
6130
  const tagId = "dsh-git-branch-manage/GitWorktreeCard.module.css";
5765
6131
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=\"" + tagId + "\"]") === null) {
5766
6132
  const tag = document.createElement("style");
@@ -5799,6 +6165,7 @@ window.__ModuleLoader__.load({
5799
6165
  "keepBad": "keepBad_1281848784",
5800
6166
  "copyFilesRow": "copyFilesRow_1281848784",
5801
6167
  "copyFilesInput": "copyFilesInput_1281848784",
6168
+ "accelInput": "accelInput_1281848784",
5802
6169
  "switchBad": "switchBad_1281848784",
5803
6170
  "historyRow": "historyRow_1281848784",
5804
6171
  "history": "history_1281848784",
@@ -5875,13 +6242,14 @@ window.__ModuleLoader__.load({
5875
6242
  const shownHistory = historyAll ? history : history.slice(0, HISTORY_PREVIEW);
5876
6243
  const { t } = props;
5877
6244
  const state = props.useGitWorktreeCard((snapshot) => snapshot);
6245
+ const accelState = props.useGitWorktreeAccel((snapshot) => snapshot);
5878
6246
  if (!state.available) return null;
5879
6247
  if (!onPage) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5880
6248
  className: GitWorktreeCard_module_default.summary,
5881
6249
  children: t("cardDescription")
5882
6250
  });
5883
6251
  const lockInput = !state.writable;
5884
- const lockActions = !state.dirty || state.saving || !state.keepWorktreesValid;
6252
+ const lockActions = !state.dirty && !accelState.dirty || state.saving || accelState.saving || !state.keepWorktreesValid || accelState.urlError !== null;
5885
6253
  const legacyRootText = storage?.legacyRoot ?? (state.rootDir === "" ? "—" : state.rootDir);
5886
6254
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5887
6255
  className: GitWorktreeCard_module_default.page,
@@ -6055,6 +6423,56 @@ window.__ModuleLoader__.load({
6055
6423
  })
6056
6424
  ]
6057
6425
  }),
6426
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6427
+ className: `${GitWorktreeCard_module_default.field} ${GitWorktreeCard_module_default.toggleRow}`,
6428
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6429
+ className: GitWorktreeCard_module_default.toggleText,
6430
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6431
+ className: GitWorktreeCard_module_default.toggleLabel,
6432
+ children: t("panel.cnAccelerate")
6433
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6434
+ className: GitWorktreeCard_module_default.toggleHint,
6435
+ children: t("accel.hint")
6436
+ })]
6437
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6438
+ className: GitWorktreeCard_module_default.toggleControl,
6439
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6440
+ className: GitWorktreeCard_module_default.toggle,
6441
+ type: "checkbox",
6442
+ disabled: lockInput || accelState.saving,
6443
+ checked: accelState.enabled,
6444
+ onChange: (event) => {
6445
+ props.setAccelEnabled(event.target.checked);
6446
+ }
6447
+ })
6448
+ })]
6449
+ }),
6450
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
6451
+ className: `${GitWorktreeCard_module_default.field} ${GitWorktreeCard_module_default.copyFilesRow}`,
6452
+ htmlFor: "git-worktree-card-accel-url",
6453
+ children: [
6454
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6455
+ className: GitWorktreeCard_module_default.fieldLabel,
6456
+ children: t("accel.urlLabel")
6457
+ }),
6458
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6459
+ id: "git-worktree-card-accel-url",
6460
+ className: GitWorktreeCard_module_default.accelInput,
6461
+ type: "text",
6462
+ spellCheck: false,
6463
+ disabled: lockInput || accelState.saving,
6464
+ value: accelState.urlText,
6465
+ onChange: (event) => {
6466
+ props.editAccelUrl(event.target.value);
6467
+ }
6468
+ }),
6469
+ accelState.urlError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6470
+ className: GitWorktreeCard_module_default.keepBad,
6471
+ role: "alert",
6472
+ children: accelState.urlError === "empty" ? t("accel.urlEmpty") : t("accel.urlInvalid")
6473
+ }) : null
6474
+ ]
6475
+ }),
6058
6476
  history.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6059
6477
  className: `${GitWorktreeCard_module_default.field} ${GitWorktreeCard_module_default.historyRow}`,
6060
6478
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -6121,19 +6539,30 @@ window.__ModuleLoader__.load({
6121
6539
  role: "status",
6122
6540
  children: t("cardSaveFailed")
6123
6541
  }) : null,
6542
+ accelState.failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6543
+ className: GitWorktreeCard_module_default.failed,
6544
+ role: "status",
6545
+ children: t("accel.saveFailed")
6546
+ }) : null,
6124
6547
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6125
6548
  type: "button",
6126
6549
  className: GitWorktreeCard_module_default.discard,
6127
6550
  disabled: lockActions,
6128
- onClick: props.discard,
6551
+ onClick: () => {
6552
+ props.discard();
6553
+ props.discardAccel();
6554
+ },
6129
6555
  children: t("cardDiscard")
6130
6556
  }),
6131
6557
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6132
6558
  type: "button",
6133
6559
  className: GitWorktreeCard_module_default.save,
6134
6560
  disabled: lockActions,
6135
- onClick: props.save,
6136
- children: t(state.saving ? "cardSaving" : "cardSave")
6561
+ onClick: () => {
6562
+ props.save();
6563
+ props.saveAccel();
6564
+ },
6565
+ children: t(state.saving || accelState.saving ? "cardSaving" : "cardSave")
6137
6566
  })
6138
6567
  ]
6139
6568
  })
@@ -6330,7 +6759,18 @@ window.__ModuleLoader__.load({
6330
6759
  "panel.stCopy": "copied",
6331
6760
  "panel.stType": "type changed",
6332
6761
  "panel.stUntrack": "untracked",
6333
- "panel.stUnmerged": "conflict"
6762
+ "panel.stUnmerged": "conflict",
6763
+ "panel.cnAccelerate": "China acceleration",
6764
+ "accel.enable": "Enable acceleration",
6765
+ "accel.hint": "Accelerates https github.com remotes for fetch/update only; push is never proxied. The mirror sees repository content — do not use it for private or sensitive repositories.",
6766
+ "accel.urlLabel": "Acceleration URL",
6767
+ "accel.restoreDefault": "Restore default",
6768
+ "accel.urlInvalid": "Enter an http(s):// URL",
6769
+ "accel.urlEmpty": "Enter an acceleration URL",
6770
+ "accel.saveFailed": "The change did not save. Try again.",
6771
+ "accel.savedOn": "China acceleration enabled: {url}",
6772
+ "accel.savedOff": "China acceleration disabled",
6773
+ "accel.mirrorDownHint": "If the mirror is down, disable acceleration or change the URL"
6334
6774
  };
6335
6775
  /** 中文词典。 */
6336
6776
  const zh = {
@@ -6520,7 +6960,18 @@ window.__ModuleLoader__.load({
6520
6960
  "panel.stCopy": "复制",
6521
6961
  "panel.stType": "类型变更",
6522
6962
  "panel.stUntrack": "未跟踪",
6523
- "panel.stUnmerged": "冲突"
6963
+ "panel.stUnmerged": "冲突",
6964
+ "panel.cnAccelerate": "国内加速",
6965
+ "accel.enable": "启用加速",
6966
+ "accel.hint": "仅加速 https 的 github.com 仓库地址,作用于提取/更新;推送不走加速。第三方加速站可看到仓库内容,请勿用于私有/敏感仓库。",
6967
+ "accel.urlLabel": "加速地址",
6968
+ "accel.restoreDefault": "恢复默认",
6969
+ "accel.urlInvalid": "请输入以 http(s):// 开头的地址",
6970
+ "accel.urlEmpty": "请输入加速地址",
6971
+ "accel.saveFailed": "保存未生效,请重试。",
6972
+ "accel.savedOn": "已启用国内加速:{url}",
6973
+ "accel.savedOff": "已关闭国内加速",
6974
+ "accel.mirrorDownHint": "若加速地址不可用,可停用国内加速或更换地址"
6524
6975
  };
6525
6976
  //#endregion
6526
6977
  //#region src/client/index.ts
@@ -6771,6 +7222,11 @@ window.__ModuleLoader__.load({
6771
7222
  });
6772
7223
  const form = new CardForm(settingsForm);
6773
7224
  const store = form.bind();
7225
+ const accelForm = new AccelForm(settingsForm);
7226
+ const accelFace = {
7227
+ store: accelForm.bind(),
7228
+ ...accelForm.actions()
7229
+ };
6774
7230
  const describeFace = ctx.configForms.describe();
6775
7231
  let configDisposer;
6776
7232
  const syncCardSeat = () => {
@@ -6781,8 +7237,12 @@ window.__ModuleLoader__.load({
6781
7237
  key: PLUGIN_PACKAGE,
6782
7238
  locale: NS,
6783
7239
  inject: () => ({
6784
- hooks: { gitWorktreeCard: store },
7240
+ hooks: {
7241
+ gitWorktreeCard: store,
7242
+ gitWorktreeAccel: accelFace.store
7243
+ },
6785
7244
  ...form.actions(),
7245
+ ...accelForm.actions(),
6786
7246
  manager: managerFace()
6787
7247
  })
6788
7248
  }, GitWorktreeCard));
@@ -6814,7 +7274,8 @@ window.__ModuleLoader__.load({
6814
7274
  component: (tabProps) => (0, react.createElement)(GitPanelTab, {
6815
7275
  ctx: betterCtx,
6816
7276
  scope: tabProps.scope,
6817
- t: panelT
7277
+ t: panelT,
7278
+ accel: accelFace
6818
7279
  })
6819
7280
  }), "git-worktree: better-sidebar tab");
6820
7281
  });
package/lib/git.d.ts CHANGED
@@ -207,6 +207,20 @@ export declare function renameBranch(exec: Exec, repoRoot: string, name: string,
207
207
  * @returns the name of the branch that was deleted.
208
208
  */
209
209
  export declare function deleteBranch(exec: Exec, repoRoot: string, name: string): Promise<string>;
210
+ /**
211
+ * argv prefix for network READ commands when cn-accelerate is on: the
212
+ * per-command `url.<base>.insteadOf=https://github.com/` rewrite. Per-command
213
+ * `git -c` (before the subcommand) rather than the user's global git config —
214
+ * the plugin must never persist settings on the user's behalf, and one
215
+ * injected value cannot leak into unrelated git runs. Rewrites
216
+ * `https://github.com/o/r.git` → `<base>https://github.com/o/r.git`, which is
217
+ * how public mirror prefixes consume GitHub URLs.
218
+ * @param prefix - the accelerate URL (raw or normalized); undefined = off.
219
+ * @returns the `-c` pair to prepend, or `[]` when off or the value is
220
+ * unusable (an invalid value fails SAFE to a direct connection — config
221
+ * validation rejects such values at load; this is the defensive path).
222
+ */
223
+ export declare function accelArgs(prefix: string | undefined): readonly string[];
210
224
  /**
211
225
  * Sync remote-tracking refs: fetch every remote and prune tracking branches
212
226
  * the remotes no longer carry (the CLI equivalent of IDEA's "synchronize
@@ -215,8 +229,10 @@ export declare function deleteBranch(exec: Exec, repoRoot: string, name: string)
215
229
  * executor's hard timeout as a GitError.
216
230
  * @param exec - executor seam.
217
231
  * @param repoRoot - main worktree directory (fetch is repository-wide).
232
+ * @param accelPrefix - optional cn-accelerate mirror prefix (see
233
+ * {@link accelArgs}); only the READ half of the network commands gets it.
218
234
  */
219
- export declare function fetchAll(exec: Exec, repoRoot: string): Promise<void>;
235
+ export declare function fetchAll(exec: Exec, repoRoot: string, accelPrefix?: string): Promise<void>;
220
236
  /** Outcome of {@link updateBranch}: the branch touched and whether it moved. */
221
237
  export interface UpdateOutcome {
222
238
  branch: string;
@@ -233,10 +249,12 @@ export interface UpdateOutcome {
233
249
  * branch ITS worktree holds.
234
250
  * @param exec - executor seam.
235
251
  * @param cwd - directory whose checked-out branch is updated.
252
+ * @param accelPrefix - optional cn-accelerate mirror prefix; feeds ONLY the
253
+ * fetch phase ({@link fetchAll}) — the `--ff-only` merge is local.
236
254
  * @returns the branch name and whether HEAD actually moved (false = the
237
255
  * upstream already contained it).
238
256
  */
239
- export declare function updateBranch(exec: Exec, cwd: string): Promise<UpdateOutcome>;
257
+ export declare function updateBranch(exec: Exec, cwd: string, accelPrefix?: string): Promise<UpdateOutcome>;
240
258
  /** Outcome of {@link pushBranch}: the branch touched and whether it moved. */
241
259
  export interface PushOutcome {
242
260
  branch: string;
@@ -249,7 +267,11 @@ export interface PushOutcome {
249
267
  * git's own message rather than silently picking one), and `--force` is
250
268
  * never passed — a diverged remote refuses rather than losing commits.
251
269
  * Runs at the queried directory's own toplevel, so a linked-worktree
252
- * session pushes the branch ITS worktree holds.
270
+ * session pushes the branch ITS worktree holds. The cn-accelerate rewrite is
271
+ * deliberately NEVER injected here (no {@link accelArgs} parameter exists):
272
+ * public mirrors have no `receive-pack`, and rewriting would turn a
273
+ * diagnosable direct failure into an opaque mirror failure while leaking the
274
+ * pushed content to a third party.
253
275
  * @param exec - executor seam.
254
276
  * @param cwd - directory whose checked-out branch is pushed.
255
277
  * @returns the branch and whether the remote-tracking ref changed. A branch
package/lib/git.js CHANGED
@@ -7,7 +7,7 @@ import { execFile } from 'node:child_process';
7
7
  import { existsSync, readdirSync } from 'node:fs';
8
8
  import { appendFile, mkdir, readFile, rm } from 'node:fs/promises';
9
9
  import { basename, dirname, isAbsolute, join, normalize, resolve } from 'node:path';
10
- import { localBranchName } from './normalize.js';
10
+ import { accelerateUrlError, localBranchName, normalizeAccelerateUrl } from './normalize.js';
11
11
  /** Failure of one git invocation: non-zero exit with the stderr text. */
12
12
  export class GitError extends Error {
13
13
  args;
@@ -464,6 +464,24 @@ export async function deleteBranch(exec, repoRoot, name) {
464
464
  await git(exec, repoRoot, ['branch', '-d', name]);
465
465
  return name;
466
466
  }
467
+ /**
468
+ * argv prefix for network READ commands when cn-accelerate is on: the
469
+ * per-command `url.<base>.insteadOf=https://github.com/` rewrite. Per-command
470
+ * `git -c` (before the subcommand) rather than the user's global git config —
471
+ * the plugin must never persist settings on the user's behalf, and one
472
+ * injected value cannot leak into unrelated git runs. Rewrites
473
+ * `https://github.com/o/r.git` → `<base>https://github.com/o/r.git`, which is
474
+ * how public mirror prefixes consume GitHub URLs.
475
+ * @param prefix - the accelerate URL (raw or normalized); undefined = off.
476
+ * @returns the `-c` pair to prepend, or `[]` when off or the value is
477
+ * unusable (an invalid value fails SAFE to a direct connection — config
478
+ * validation rejects such values at load; this is the defensive path).
479
+ */
480
+ export function accelArgs(prefix) {
481
+ if (prefix === undefined || accelerateUrlError(prefix) !== undefined)
482
+ return [];
483
+ return ['-c', `url.${normalizeAccelerateUrl(prefix)}.insteadOf=https://github.com/`];
484
+ }
467
485
  /**
468
486
  * Sync remote-tracking refs: fetch every remote and prune tracking branches
469
487
  * the remotes no longer carry (the CLI equivalent of IDEA's "synchronize
@@ -472,9 +490,11 @@ export async function deleteBranch(exec, repoRoot, name) {
472
490
  * executor's hard timeout as a GitError.
473
491
  * @param exec - executor seam.
474
492
  * @param repoRoot - main worktree directory (fetch is repository-wide).
493
+ * @param accelPrefix - optional cn-accelerate mirror prefix (see
494
+ * {@link accelArgs}); only the READ half of the network commands gets it.
475
495
  */
476
- export async function fetchAll(exec, repoRoot) {
477
- await git(exec, repoRoot, ['fetch', '--all', '--prune'], NETWORK_TIMEOUT_MS);
496
+ export async function fetchAll(exec, repoRoot, accelPrefix) {
497
+ await git(exec, repoRoot, [...accelArgs(accelPrefix), 'fetch', '--all', '--prune'], NETWORK_TIMEOUT_MS);
478
498
  }
479
499
  /**
480
500
  * Update the CURRENT checkout: fetch every remote, then fast-forward the
@@ -487,11 +507,13 @@ export async function fetchAll(exec, repoRoot) {
487
507
  * branch ITS worktree holds.
488
508
  * @param exec - executor seam.
489
509
  * @param cwd - directory whose checked-out branch is updated.
510
+ * @param accelPrefix - optional cn-accelerate mirror prefix; feeds ONLY the
511
+ * fetch phase ({@link fetchAll}) — the `--ff-only` merge is local.
490
512
  * @returns the branch name and whether HEAD actually moved (false = the
491
513
  * upstream already contained it).
492
514
  */
493
- export async function updateBranch(exec, cwd) {
494
- await fetchAll(exec, cwd);
515
+ export async function updateBranch(exec, cwd, accelPrefix) {
516
+ await fetchAll(exec, cwd, accelPrefix);
495
517
  const before = (await git(exec, cwd, ['rev-parse', 'HEAD'])).trim();
496
518
  await git(exec, cwd, ['merge', '--ff-only', '@{u}']);
497
519
  const after = (await git(exec, cwd, ['rev-parse', 'HEAD'])).trim();
@@ -505,7 +527,11 @@ export async function updateBranch(exec, cwd) {
505
527
  * git's own message rather than silently picking one), and `--force` is
506
528
  * never passed — a diverged remote refuses rather than losing commits.
507
529
  * Runs at the queried directory's own toplevel, so a linked-worktree
508
- * session pushes the branch ITS worktree holds.
530
+ * session pushes the branch ITS worktree holds. The cn-accelerate rewrite is
531
+ * deliberately NEVER injected here (no {@link accelArgs} parameter exists):
532
+ * public mirrors have no `receive-pack`, and rewriting would turn a
533
+ * diagnosable direct failure into an opaque mirror failure while leaking the
534
+ * pushed content to a third party.
509
535
  * @param exec - executor seam.
510
536
  * @param cwd - directory whose checked-out branch is pushed.
511
537
  * @returns the branch and whether the remote-tracking ref changed. A branch
package/lib/index.d.ts CHANGED
@@ -25,6 +25,11 @@ export interface Config {
25
25
  /** Post-create configuration files to copy when .worktreeinclude is absent;
26
26
  * absent = none. */
27
27
  postCreateCopyFiles?: Volatile<string[]> | string[];
28
+ /** cn-accelerate mirror rewrite for network READ commands (fetch/update);
29
+ * absent = off. PUSH is never rewritten. */
30
+ cnAccelerate?: Volatile<boolean> | boolean;
31
+ /** cn-accelerate mirror prefix; absent = the shipped default. */
32
+ cnAccelerateUrl?: Volatile<string> | string;
28
33
  }
29
34
  /** The shipped prune cap (whole storage root, valid git worktrees only). */
30
35
  export declare const KEEP_WORKTREES_DEFAULT = 30;
@@ -36,6 +41,13 @@ export declare const KEEP_WORKTREES_DEFAULT = 30;
36
41
  export declare const Config: z;
37
42
  /** Reject stale or misspelled config keys before defaults can hide them. */
38
43
  export declare function validateConfig(config: Config): void;
44
+ /**
45
+ * The accelerate prefix must be an http(s) URL without whitespace — it is
46
+ * concatenated with `https://github.com/` into one `url.<base>.insteadOf`
47
+ * config value, so a space would smuggle arguments and a non-http scheme is
48
+ * not a fetchable remote. Absent = the shipped default, which passes.
49
+ */
50
+ export declare function validateCnAccelerateUrl(value: string | undefined): void;
39
51
  /**
40
52
  * The prune cap must be an integer >= 1: 0 would read as "create then
41
53
  * immediately self-delete" (the fresh worktree is excluded, but every other
@@ -60,6 +72,10 @@ export interface SectionConfig {
60
72
  keepWorktrees?: number;
61
73
  /** Post-create configuration files to copy when .worktreeinclude is absent. */
62
74
  postCreateCopyFiles?: string[];
75
+ /** cn-accelerate mirror rewrite for network READ commands; absent = off. */
76
+ cnAccelerate?: boolean;
77
+ /** cn-accelerate mirror prefix; absent = the shipped default. */
78
+ cnAccelerateUrl?: string;
63
79
  }
64
80
  /** The section-shaped view of a config: absent keys stay absent
65
81
  * (`exactOptionalPropertyTypes`) except the switches, which spell their
package/lib/index.js CHANGED
@@ -8,6 +8,7 @@
8
8
  import { homedir } from 'node:os';
9
9
  import z from '@deepseek-ai/schemastery';
10
10
  import { childProcessExec } from './git.js';
11
+ import { ACCEL_DEFAULT_URL, accelerateUrlError } from './normalize.js';
11
12
  import { handleAiCommitMessage, handleChanges, handleCommit, handleCommitFiles, handleCreateBranch, handleCreateWorktree, handleDeleteBranch, handleEnsureDirectory, handleFetch, handleGroupWorktrees, handleInspectWorktree, handleLog, handlePathExists, handlePurgeDirectory, handleRemoveWorktree, handleRenameBranch, handleStage, handleStatus, handleSwitch, handleUpdate, handlePush, handleWorktreesAll, } from './routes.js';
12
13
  import { validateRootDir } from './settings.js';
13
14
  import { ROUTE_AI_COMMIT_MESSAGE, ROUTE_BRANCH, ROUTE_BRANCH_DELETE, ROUTE_BRANCH_RENAME, ROUTE_CHANGES, ROUTE_COMMIT, ROUTE_COMMIT_FILES, ROUTE_ENSURE_DIRECTORY, ROUTE_EXISTS, ROUTE_FETCH, ROUTE_GROUP, ROUTE_INSPECT, ROUTE_LOG, ROUTE_PUSH, ROUTE_PURGE, ROUTE_REMOVE, ROUTE_STAGE, ROUTE_STATUS, ROUTE_SWITCH, ROUTE_UPDATE, ROUTE_WORKTREE, ROUTE_WORKTREES_ALL } from './wire.js';
@@ -37,10 +38,12 @@ export const Config = z.object({
37
38
  autoPruneWorktrees: z.boolean().default(false).volatile(),
38
39
  keepWorktrees: z.number().default(KEEP_WORKTREES_DEFAULT).volatile(),
39
40
  postCreateCopyFiles: z.array(z.string()).default([]).volatile(),
41
+ cnAccelerate: z.boolean().default(false).volatile(),
42
+ cnAccelerateUrl: z.string().default(ACCEL_DEFAULT_URL).volatile(),
40
43
  });
41
44
  /** Reject stale or misspelled config keys before defaults can hide them. */
42
45
  export function validateConfig(config) {
43
- const unknown = Object.keys(config).find(key => key !== 'rootDir' && key !== 'groupSidebar' && key !== 'fetchBeforeCreate' && key !== 'autoPruneWorktrees' && key !== 'keepWorktrees' && key !== 'postCreateCopyFiles');
46
+ const unknown = Object.keys(config).find(key => key !== 'rootDir' && key !== 'groupSidebar' && key !== 'fetchBeforeCreate' && key !== 'autoPruneWorktrees' && key !== 'keepWorktrees' && key !== 'postCreateCopyFiles' && key !== 'cnAccelerate' && key !== 'cnAccelerateUrl');
44
47
  if (unknown !== undefined) {
45
48
  throw new Error(`GitWorktreeConfig: unknown key "${unknown}"`);
46
49
  }
@@ -66,9 +69,27 @@ export function validateConfig(config) {
66
69
  throw new Error('GitWorktreeConfig: "postCreateCopyFiles" must be an array of strings');
67
70
  }
68
71
  }
72
+ const cnAccelerate = readVolatile(config.cnAccelerate);
73
+ if (cnAccelerate !== undefined && typeof cnAccelerate !== 'boolean') {
74
+ throw new Error('GitWorktreeConfig: "cnAccelerate" must be a boolean');
75
+ }
76
+ validateCnAccelerateUrl(readVolatile(config.cnAccelerateUrl));
69
77
  validateKeepWorktrees(readVolatile(config.keepWorktrees));
70
78
  validateRootDir(root);
71
79
  }
80
+ /**
81
+ * The accelerate prefix must be an http(s) URL without whitespace — it is
82
+ * concatenated with `https://github.com/` into one `url.<base>.insteadOf`
83
+ * config value, so a space would smuggle arguments and a non-http scheme is
84
+ * not a fetchable remote. Absent = the shipped default, which passes.
85
+ */
86
+ export function validateCnAccelerateUrl(value) {
87
+ if (value === undefined)
88
+ return;
89
+ if (typeof value !== 'string' || accelerateUrlError(value) !== undefined) {
90
+ throw new Error('GitWorktreeConfig: "cnAccelerateUrl" must be an http(s) URL without whitespace');
91
+ }
92
+ }
72
93
  /**
73
94
  * The prune cap must be an integer >= 1: 0 would read as "create then
74
95
  * immediately self-delete" (the fresh worktree is excluded, but every other
@@ -94,14 +115,18 @@ export function sectionOf(config) {
94
115
  const fetchBeforeCreate = readVolatile(config.fetchBeforeCreate);
95
116
  const autoPruneWorktrees = readVolatile(config.autoPruneWorktrees);
96
117
  const keepWorktrees = readVolatile(config.keepWorktrees);
118
+ const cnAccelerate = readVolatile(config.cnAccelerate);
119
+ const cnAccelerateUrl = readVolatile(config.cnAccelerateUrl);
97
120
  return {
98
121
  ...(rootDir === undefined ? {} : { rootDir }),
99
122
  ...(postCreateCopyFiles === undefined ? {} : { postCreateCopyFiles }),
123
+ ...(cnAccelerateUrl === undefined ? {} : { cnAccelerateUrl }),
100
124
  // The composition layer spells the shipped defaults so a user-layer
101
125
  // unset can always fall back to them.
102
126
  groupSidebar: groupSidebar ?? true,
103
127
  fetchBeforeCreate: fetchBeforeCreate ?? false,
104
128
  autoPruneWorktrees: autoPruneWorktrees ?? false,
129
+ cnAccelerate: cnAccelerate ?? false,
105
130
  keepWorktrees: keepWorktrees ?? KEEP_WORKTREES_DEFAULT,
106
131
  };
107
132
  }
@@ -119,6 +144,8 @@ export function apply(ctx, config = {}) {
119
144
  sectionRootDir: () => readVolatile(currentConfig().rootDir),
120
145
  sectionFetchBeforeCreate: () => readVolatile(currentConfig().fetchBeforeCreate),
121
146
  sectionPostCreateCopyFiles: () => readVolatile(currentConfig().postCreateCopyFiles),
147
+ sectionCnAccelerate: () => readVolatile(currentConfig().cnAccelerate),
148
+ sectionCnAccelerateUrl: () => readVolatile(currentConfig().cnAccelerateUrl),
122
149
  home: () => homedir(),
123
150
  envHome: () => process.env.DSH_HOME,
124
151
  });
@@ -1,7 +1,8 @@
1
1
  /**
2
- * Pure branch/path name normalization for worktree directory placement.
3
- * Windows-forbidden characters are the governing constraint: a worktree
4
- * folder name must survive on every platform the host runs on.
2
+ * Pure name/URL normalization shared by the host (git command assembly,
3
+ * config validation) and the client (forms). Windows-forbidden characters
4
+ * govern worktree directory placement: a worktree folder name must survive
5
+ * on every platform the host runs on.
5
6
  */
6
7
  /**
7
8
  * Turn a branch display name into a single safe directory segment.
@@ -56,3 +57,27 @@ export type BranchNameIssue = 'empty' | 'leadingDash' | 'illegal';
56
57
  * @returns the issue kind, or null when the name is acceptable.
57
58
  */
58
59
  export declare function branchNameIssue(name: string): BranchNameIssue | null;
60
+ /**
61
+ * Shipped default for the cn-accelerate mirror prefix (the design doc's
62
+ * `cnAccelerateUrl` default and the popover's「恢复默认」target).
63
+ */
64
+ export declare const ACCEL_DEFAULT_URL = "https://gh-proxy.com/";
65
+ /** Why a user-typed accelerate URL is not acceptable yet (undefined = fine). */
66
+ export type AccelerateUrlIssue = 'empty' | 'invalid';
67
+ /**
68
+ * Pre-flight check of the cn-accelerate prefix against what the rewrite can
69
+ * actually use: non-empty after trim, `http://` or `https://` scheme, and no
70
+ * whitespace anywhere (a space would break the `url.<base>.insteadOf=<target>`
71
+ * git config value and smuggle a second argument).
72
+ * @param value - raw draft text.
73
+ * @returns the issue kind, or undefined when the value is acceptable.
74
+ */
75
+ export declare function accelerateUrlError(value: string): AccelerateUrlIssue | undefined;
76
+ /**
77
+ * Normalize an accepted accelerate URL: trim, and append the trailing `/`
78
+ * the prefix concatenation requires (`https://gh-proxy.com/` + `o/r.git`
79
+ * must stay one valid URL). Callers pass the result to `url.<base>.insteadOf`.
80
+ * @param value - an accepted (non-empty, http(s), whitespace-free) URL.
81
+ * @returns the prefix ending in `/`.
82
+ */
83
+ export declare function normalizeAccelerateUrl(value: string): string;
package/lib/normalize.js CHANGED
@@ -1,7 +1,8 @@
1
1
  /**
2
- * Pure branch/path name normalization for worktree directory placement.
3
- * Windows-forbidden characters are the governing constraint: a worktree
4
- * folder name must survive on every platform the host runs on.
2
+ * Pure name/URL normalization shared by the host (git command assembly,
3
+ * config validation) and the client (forms). Windows-forbidden characters
4
+ * govern worktree directory placement: a worktree folder name must survive
5
+ * on every platform the host runs on.
5
6
  */
6
7
  /**
7
8
  * Turn a branch display name into a single safe directory segment.
@@ -91,3 +92,37 @@ export function branchNameIssue(name) {
91
92
  return 'illegal';
92
93
  return null;
93
94
  }
95
+ /**
96
+ * Shipped default for the cn-accelerate mirror prefix (the design doc's
97
+ * `cnAccelerateUrl` default and the popover's「恢复默认」target).
98
+ */
99
+ export const ACCEL_DEFAULT_URL = 'https://gh-proxy.com/';
100
+ /**
101
+ * Pre-flight check of the cn-accelerate prefix against what the rewrite can
102
+ * actually use: non-empty after trim, `http://` or `https://` scheme, and no
103
+ * whitespace anywhere (a space would break the `url.<base>.insteadOf=<target>`
104
+ * git config value and smuggle a second argument).
105
+ * @param value - raw draft text.
106
+ * @returns the issue kind, or undefined when the value is acceptable.
107
+ */
108
+ export function accelerateUrlError(value) {
109
+ const trimmed = value.trim();
110
+ if (trimmed === '')
111
+ return 'empty';
112
+ if (/\s/.test(trimmed))
113
+ return 'invalid';
114
+ if (!trimmed.startsWith('http://') && !trimmed.startsWith('https://'))
115
+ return 'invalid';
116
+ return undefined;
117
+ }
118
+ /**
119
+ * Normalize an accepted accelerate URL: trim, and append the trailing `/`
120
+ * the prefix concatenation requires (`https://gh-proxy.com/` + `o/r.git`
121
+ * must stay one valid URL). Callers pass the result to `url.<base>.insteadOf`.
122
+ * @param value - an accepted (non-empty, http(s), whitespace-free) URL.
123
+ * @returns the prefix ending in `/`.
124
+ */
125
+ export function normalizeAccelerateUrl(value) {
126
+ const trimmed = value.trim();
127
+ return trimmed.endsWith('/') ? trimmed : `${trimmed}/`;
128
+ }
package/lib/routes.d.ts CHANGED
@@ -14,6 +14,10 @@ export interface RouteDeps {
14
14
  sectionFetchBeforeCreate: () => boolean | undefined;
15
15
  /** Post-create configuration files to copy when .worktreeinclude is absent. */
16
16
  sectionPostCreateCopyFiles?: () => string[] | undefined;
17
+ /** Whether the cn-accelerate mirror rewrite is on (absent = off). */
18
+ sectionCnAccelerate?: () => boolean | undefined;
19
+ /** The cn-accelerate mirror prefix (absent = the shipped default). */
20
+ sectionCnAccelerateUrl?: () => string | undefined;
17
21
  /** User home directory seam. */
18
22
  home: () => string;
19
23
  /** `$DSH_HOME` environment value seam. */
package/lib/routes.js CHANGED
@@ -6,7 +6,7 @@ import { basename, dirname, join, resolve } from 'node:path';
6
6
  import { mkdir, readdir, rm, stat } from 'node:fs/promises';
7
7
  import { runPostCreateCopy } from './copy-files.js';
8
8
  import { appendWorktreeExclude, fsExcludeSeams, GitError, addWorktree, addWorktreeCutout, collectAiDiff, commitFiles, commitIndex, createBranch, cutoutBranchName, deleteBranch, fetchAll, fsDirExists, inspectWorktree, isAbsoluteDir, isStageablePath, logHistory, probeRepo, probeWorkspaceGit, removeWorktree, renameBranch, resolveBranch, pushBranch, stagePaths, switchBranch, updateBranch, workingChanges } from './git.js';
9
- import { isAbsoluteConfigPath, sanitizeBranchDir } from './normalize.js';
9
+ import { ACCEL_DEFAULT_URL, accelerateUrlError, isAbsoluteConfigPath, normalizeAccelerateUrl, sanitizeBranchDir } from './normalize.js';
10
10
  import { resolveRootDir } from './settings.js';
11
11
  import { COMMIT_MESSAGE_LIMIT } from './wire.js';
12
12
  /** Real fs-backed one-level directory listing (names only). Plain FILES are
@@ -39,6 +39,18 @@ export async function fsMkdirRecursive(path) {
39
39
  function fail(status, error) {
40
40
  return { status, body: { error } };
41
41
  }
42
+ /**
43
+ * The rewrite prefix for one request's network READ commands: the cn-accelerate
44
+ * mirror prefix when the switch is on and the URL setting is usable (the
45
+ * shipped default fills an absent URL), else undefined — no injection and the
46
+ * direct connection runs unchanged. PUSH never consults this helper.
47
+ */
48
+ function accelPrefix(deps) {
49
+ if (deps.sectionCnAccelerate?.() !== true)
50
+ return undefined;
51
+ const url = deps.sectionCnAccelerateUrl?.() ?? ACCEL_DEFAULT_URL;
52
+ return accelerateUrlError(url) === undefined ? normalizeAccelerateUrl(url) : undefined;
53
+ }
42
54
  /** Upper bound on distinct paths one /group request may probe. */
43
55
  const GROUP_PATHS_LIMIT = 256;
44
56
  /** Probes per in-flight batch — polite on Windows, where each git call is a
@@ -414,7 +426,7 @@ export async function handleCreateWorktree(deps, body) {
414
426
  const resolved = await resolveBranch(deps.exec, facts.repoRoot, branch);
415
427
  if (resolved?.remote !== undefined) {
416
428
  try {
417
- await fetchAll(deps.exec, facts.repoRoot);
429
+ await fetchAll(deps.exec, facts.repoRoot, accelPrefix(deps));
418
430
  }
419
431
  catch (error) {
420
432
  fetchWarning = error instanceof GitError ? error.stderr.trim() : error instanceof Error ? error.message : String(error);
@@ -649,7 +661,7 @@ export async function handleFetch(deps, body) {
649
661
  const facts = await probeRepo(deps.exec, repoPath, deps.dirExists);
650
662
  if (facts === undefined)
651
663
  return fail(400, `"${repoPath}" is not inside a git repository`);
652
- await fetchAll(deps.exec, facts.repoRoot);
664
+ await fetchAll(deps.exec, facts.repoRoot, accelPrefix(deps));
653
665
  const result = { remote: 'all' };
654
666
  return { status: 200, body: result };
655
667
  }
@@ -682,7 +694,7 @@ export async function handleUpdate(deps, body) {
682
694
  // session "update current branch" means the branch that worktree holds.
683
695
  // `repoRoot` would fast-forward the main checkout behind the user's back
684
696
  // (both are the same directory when the session IS the main checkout).
685
- const outcome = await updateBranch(deps.exec, facts.toplevel);
697
+ const outcome = await updateBranch(deps.exec, facts.toplevel, accelPrefix(deps));
686
698
  const result = { branch: outcome.branch, updated: outcome.updated };
687
699
  return { status: 200, body: result };
688
700
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-git-branch-manage",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "提供 git 分支与 worktree 管理:输入框工具栏显示当前分支、可原地切换;支持 IDEA 风格分支选择器、远程分支检出、右键操作菜单(新建/重命名/删除/复制)、推送与更新当前分支,以及把分支隔离到独立 worktree 工作区,并管理其创建与删除。",
5
5
  "keywords": [
6
6
  "dsh",