dsh-git-branch-manage 1.0.7 → 1.0.8

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
@@ -131,6 +131,23 @@ window.__ModuleLoader__.load({
131
131
  const ROUTE_TAG = `${ROUTE_PREFIX}/tag`;
132
132
  /** POST ROUTE_PREFIX/tag-push — push ONE existing local tag to its remote. */
133
133
  const ROUTE_TAG_PUSH = `${ROUTE_PREFIX}/tag-push`;
134
+ /** POST ROUTE_PREFIX/credentials — the global per-host credentials, secrets
135
+ * masked (the stored secret never leaves the host). */
136
+ const ROUTE_CREDENTIALS = `${ROUTE_PREFIX}/credentials`;
137
+ /** POST ROUTE_PREFIX/credentials-save — create or update one host credential. */
138
+ const ROUTE_CREDENTIALS_SAVE = `${ROUTE_PREFIX}/credentials-save`;
139
+ /** POST ROUTE_PREFIX/credentials-delete — forget one host credential. */
140
+ const ROUTE_CREDENTIALS_DELETE = `${ROUTE_PREFIX}/credentials-delete`;
141
+ /** POST ROUTE_PREFIX/credentials-oauth-start — begin a browser sign-in for
142
+ * one host (GitHub device flow); answers the one-time user code. */
143
+ const ROUTE_CREDENTIALS_OAUTH_START = `${ROUTE_PREFIX}/credentials-oauth-start`;
144
+ /** POST ROUTE_PREFIX/credentials-oauth-poll — poll a started browser sign-in;
145
+ * stores the token and answers the fresh masked list once the user approves. */
146
+ const ROUTE_CREDENTIALS_OAUTH_POLL = `${ROUTE_PREFIX}/credentials-oauth-poll`;
147
+ /** POST ROUTE_PREFIX/remotes — the repository's configured remotes. */
148
+ const ROUTE_REMOTES = `${ROUTE_PREFIX}/remotes`;
149
+ /** POST ROUTE_PREFIX/remote — add, re-point or remove ONE remote. */
150
+ const ROUTE_REMOTE = `${ROUTE_PREFIX}/remote`;
134
151
  /** POST ROUTE_PREFIX/ai-commit-message — one-shot LLM commit message from
135
152
  * the staged diff (mounted only where the host provides the llm service). */
136
153
  const ROUTE_AI_COMMIT_MESSAGE = `${ROUTE_PREFIX}/ai-commit-message`;
@@ -138,6 +155,10 @@ window.__ModuleLoader__.load({
138
155
  * bodies and the client caps its input at the same value, so the two ends
139
156
  * can never disagree. */
140
157
  const COMMIT_MESSAGE_LIMIT = 2e3;
158
+ /** Hosts whose browser sign-in the credential dialog offers. Mirrors the
159
+ * host-side device-flow support (`src/oauth.ts`); a shared constant keeps the
160
+ * browser list from drifting away from what the host can actually do. */
161
+ const BROWSER_SIGN_IN_HOSTS = ["github.com"];
141
162
  //#endregion
142
163
  //#region src/client/api.ts
143
164
  /** POST one JSON body and parse the uniform envelope. */
@@ -447,6 +468,60 @@ window.__ModuleLoader__.load({
447
468
  });
448
469
  }
449
470
  /**
471
+ * Every stored GLOBAL credential, secrets masked. One row per host: a token
472
+ * saved for github.com serves every repository that talks to github.com.
473
+ */
474
+ function requestCredentials() {
475
+ return post(ROUTE_CREDENTIALS, {});
476
+ }
477
+ /**
478
+ * Create or update the credential of ONE host. A blank `secret` on an
479
+ * existing host keeps the stored secret.
480
+ * @param body - `{ host, username, method, secret? }`.
481
+ */
482
+ function requestSaveCredential(body) {
483
+ return post(ROUTE_CREDENTIALS_SAVE, body);
484
+ }
485
+ /**
486
+ * Forget ONE host's credential.
487
+ * @param body - `{ host }`.
488
+ */
489
+ function requestDeleteCredential(body) {
490
+ return post(ROUTE_CREDENTIALS_DELETE, body);
491
+ }
492
+ /**
493
+ * Start a browser sign-in for ONE host (GitHub device flow). The answer
494
+ * carries the one-time user code the dialog shows and the verification page
495
+ * it opens.
496
+ * @param body - `{ host }`.
497
+ */
498
+ function requestOAuthStart(body) {
499
+ return post(ROUTE_CREDENTIALS_OAUTH_START, body);
500
+ }
501
+ /**
502
+ * Poll a started browser sign-in. Resolves `{ status: 'pending' }` until the
503
+ * user approves; then the token is stored host-side and the fresh masked
504
+ * list comes back (the token itself never reaches the browser).
505
+ * @param body - `{ host, deviceCode }`.
506
+ */
507
+ function requestOAuthPoll(body) {
508
+ return post(ROUTE_CREDENTIALS_OAUTH_POLL, body);
509
+ }
510
+ /**
511
+ * The repository's configured remotes (fetch/push URLs).
512
+ * @param repoPath - absolute directory inside the repository.
513
+ */
514
+ function requestRemotes(repoPath) {
515
+ return post(ROUTE_REMOTES, { repoPath });
516
+ }
517
+ /**
518
+ * Add, re-point or remove one remote; resolves with the fresh list.
519
+ * @param body - `{ repoPath, action, name, url? }`.
520
+ */
521
+ function requestMutateRemote(body) {
522
+ return post(ROUTE_REMOTE, body);
523
+ }
524
+ /**
450
525
  * Generate a commit message from the checkout's diff with the requesting
451
526
  * session's own model (a one-shot host llm call).
452
527
  * @param body - the repo path plus the session's model selection and an
@@ -3869,7 +3944,7 @@ window.__ModuleLoader__.load({
3869
3944
  }
3870
3945
  //#endregion
3871
3946
  //#region \0git-worktree-css:/home/runner/work/dsh-git-branch/dsh-git-branch/src/client/GitHistoryPanel.module.css?inline
3872
- 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/* The head carries the title plus three filters (branch, author, time). The\n * row wraps at narrow widths rather than squeezing the date inputs into\n * unreadable slivers. */\n.historyFilters_427795137 {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n justify-content: flex-end;\n}\n\n.filterSelect_427795137,\n.filterInput_427795137,\n.filterDate_427795137 {\n height: 26px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 7px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 12px;\n padding: 0 6px;\n outline: none;\n}\n\n.filterSelect_427795137 {\n max-width: 150px;\n}\n\n.filterInput_427795137 {\n width: 120px;\n}\n\n.filterInput_427795137::placeholder {\n color: var(--dsw-alias-label-tertiary);\n}\n\n.filterDate_427795137 {\n width: 122px;\n color-scheme: light dark;\n}\n\n.filterDash_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n}\n\n.commitList_427795137 {\n /* Keep the history body gap between blocks, never between graph rows.\n * A non-shrinking block also prevents a long history from compressing rows\n * inside the card's scrolling flex body. */\n flex: none;\n}\n\n.commitRow_427795137 {\n position: relative;\n display: grid;\n grid-template-columns: var(--graph-w, 22px) minmax(0, 1fr) auto;\n column-gap: 8px;\n align-items: center;\n padding: 0 8px 0 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/* The SVG fills the actual grid row, including the text cells' padding.\n * Its paths scale vertically; the separate dot keeps a constant radius. */\n.commitGraph_427795137 {\n position: relative;\n align-self: stretch;\n min-height: 44px;\n}\n\n.commitGraphSvg_427795137 {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n display: block;\n overflow: visible;\n}\n\n.commitDot_427795137 {\n position: absolute;\n top: 50%;\n width: 8px;\n height: 8px;\n box-sizing: border-box;\n border: 2px solid currentColor;\n border-radius: 50%;\n transform: translate(-50%, -50%);\n pointer-events: none;\n}\n\n.commitExpandGraph_427795137 {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n display: block;\n pointer-events: none;\n}\n\n.commitMain_427795137 {\n padding: 6px 0;\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 min-width: 0;\n}\n\n.commitAuthor_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.commitSide_427795137 {\n padding: 6px 0;\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/* The absolute stamp column: tabular figures so every row's digits line up\n * under the one above, which is what makes the column scannable. */\n.commitDate_427795137 {\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n.commitHash_427795137 {\n font-family: ui-monospace, Consolas, monospace;\n}\n\n.commitExpand_427795137 {\n position: relative;\n grid-column: 1 / -1;\n /* Align the expanded file list with the subject column, whatever width the\n * graph band needs in this repository. */\n padding: 2px 4px 8px calc(var(--graph-w, 22px) + 8px);\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";
3947
+ 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/* The head carries the title plus three filters (branch, author, time). The\n * row wraps at narrow widths rather than squeezing the date inputs into\n * unreadable slivers. */\n.historyFilters_427795137 {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n justify-content: flex-end;\n}\n\n.filterSelect_427795137,\n.filterInput_427795137,\n.filterDate_427795137 {\n height: 26px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 7px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 12px;\n padding: 0 6px;\n outline: none;\n}\n\n.filterSelect_427795137 {\n max-width: 150px;\n}\n\n.filterInput_427795137 {\n width: 120px;\n}\n\n.filterInput_427795137::placeholder {\n color: var(--dsw-alias-label-tertiary);\n}\n\n.filterDate_427795137 {\n width: 122px;\n color-scheme: light dark;\n}\n\n.filterDash_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n}\n\n.commitList_427795137 {\n /* Keep the history body gap between blocks, never between graph rows.\n * A non-shrinking block also prevents a long history from compressing rows\n * inside the card's scrolling flex body. */\n flex: none;\n}\n\n.commitRow_427795137 {\n position: relative;\n display: grid;\n grid-template-columns: var(--graph-w, 22px) minmax(0, 1fr) auto;\n column-gap: 8px;\n align-items: center;\n padding: 0 8px 0 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/* The SVG fills the actual grid row, including the text cells' padding.\n * Its paths scale vertically; the separate dot keeps a constant radius. */\n.commitGraph_427795137 {\n position: relative;\n align-self: stretch;\n min-height: 44px;\n}\n\n.commitGraphSvg_427795137 {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n display: block;\n overflow: visible;\n}\n\n.commitDot_427795137 {\n position: absolute;\n top: 50%;\n width: 8px;\n height: 8px;\n box-sizing: border-box;\n border: 2px solid currentColor;\n border-radius: 50%;\n transform: translate(-50%, -50%);\n pointer-events: none;\n}\n\n.commitExpandGraph_427795137 {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n display: block;\n pointer-events: none;\n}\n\n.commitMain_427795137 {\n padding: 6px 0;\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 min-width: 0;\n}\n\n.commitAuthor_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.commitSide_427795137 {\n padding: 6px 0;\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/* The absolute stamp column: tabular figures so every row's digits line up\n * under the one above, which is what makes the column scannable. */\n.commitDate_427795137 {\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n.commitHash_427795137 {\n font-family: ui-monospace, Consolas, monospace;\n}\n\n.commitExpand_427795137 {\n position: relative;\n grid-column: 1 / -1;\n /* Align the expanded file list with the subject column, whatever width the\n * graph band needs in this repository. */\n padding: 2px 4px 8px calc(var(--graph-w, 22px) + 8px);\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\n/* Credential + remote dialogs. Both share one scaffold: a compact list of\n * stored rows above a single inline editor (the panel never stacks modals),\n * so the classes are named for the credential case and reused by remotes. */\n\n.credHint_427795137 {\n margin: 0 0 10px;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-secondary);\n}\n\n.credList_427795137 {\n display: flex;\n flex-direction: column;\n gap: 2px;\n max-height: 220px;\n overflow-y: auto;\n margin-bottom: 4px;\n}\n\n.credRow_427795137 {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 8px;\n border-radius: 8px;\n}\n\n.credRow_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.credMain_427795137 {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n flex: 1;\n}\n\n.credHost_427795137 {\n font-size: 13px;\n font-weight: 500;\n color: var(--dsw-alias-label-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.credMeta_427795137 {\n font-size: 11.5px;\n color: var(--dsw-alias-label-tertiary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.credMeta_427795137 code {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n}\n\n.credActions_427795137 {\n display: flex;\n gap: 2px;\n flex: none;\n}\n\n.credDivider_427795137 {\n height: 0.5px;\n background: var(--dsw-alias-border-l2);\n margin: 10px 0;\n}\n\n.credFormTitle_427795137 {\n font-size: 12px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary);\n margin-bottom: 8px;\n}\n\n/* The editor's fields: the Modal body has no gap of its own, so the stack\n * carries the rhythm instead of one margin per field. */\n.credForm_427795137 {\n display: flex;\n flex-direction: column;\n gap: 10px;\n margin-top: 2px;\n}\n\n.credPresets_427795137 {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n}\n\n.credPresetLabel_427795137 {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n margin-right: 2px;\n}\n\n.credPreset_427795137,\n.credPresetOn_427795137 {\n font: inherit;\n font-size: 12px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 999px;\n padding: 2px 10px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n}\n\n.credPreset_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.credPresetOn_427795137 {\n border-color: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-brand-primary);\n}\n\n.credMethodRow_427795137 {\n display: flex;\n gap: 6px;\n}\n\n.credMethod_427795137,\n.credMethodOn_427795137 {\n font: inherit;\n font-size: 12px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n padding: 4px 12px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n}\n\n.credMethodOn_427795137 {\n border-color: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-brand-primary);\n}\n\n.credFieldHead_427795137 {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 8px;\n}\n\n.credLink_427795137 {\n font-size: 12px;\n color: var(--dsw-alias-brand-primary);\n text-decoration: none;\n}\n\n.credLink_427795137:hover {\n text-decoration: underline;\n}\n\n/* The token field's trailing 「创建」 button: the input keeps the row's width\n * and the button hugs its end, mirroring the browser token page it opens. */\n.credSecretRow_427795137 {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.credSecretRow_427795137 .formInput_427795137 {\n flex: 1;\n min-width: 0;\n}\n\n/* The browser sign-in block: the only control on that authentication path —\n * no username or secret field sits above it any more. */\n.credOAuth_427795137 {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n/* The browser sign-in block: the one-time code the user types into the page\n * the dialog just opened, plus its waiting state. */\n.credDevice_427795137 {\n display: flex;\n flex-direction: column;\n gap: 5px;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.credDeviceRow_427795137 {\n display: flex;\n align-items: baseline;\n gap: 8px;\n}\n\n.credDeviceCode_427795137 {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 15px;\n font-weight: 600;\n letter-spacing: 1px;\n color: var(--dsw-alias-label-primary);\n}\n\n.credDeviceHint_427795137 {\n font-size: 11.5px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.credDeviceWaiting_427795137 {\n font-size: 11.5px;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* The editor's action row: both actions sit on the right, cancel nearest the\n * form, save outermost. Only while the form itself is open. */\n.credFooter_427795137 {\n display: flex;\n justify-content: flex-end;\n align-items: center;\n gap: 6px;\n margin-top: 10px;\n}\n\n.credTop_427795137 {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n margin-bottom: 10px;\n}\n\n.credTop_427795137 .credHint_427795137 {\n flex: 1;\n margin: 0;\n}\n\n.credAdd_427795137 {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: 26px;\n height: 26px;\n padding: 0;\n border: 0.5px solid var(--dsw-alias-border-l2);\n border-radius: 7px;\n background: none;\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n font-size: 16px;\n line-height: 1;\n cursor: pointer;\n}\n\n.credAdd_427795137:hover {\n border-color: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-brand-primary);\n}\n";
3873
3948
  const tagId$2 = "dsh-git-branch-manage/GitHistoryPanel.module.css";
3874
3949
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=\"" + tagId$2 + "\"]") === null) {
3875
3950
  const tag = document.createElement("style");
@@ -4006,7 +4081,36 @@ window.__ModuleLoader__.load({
4006
4081
  "fileDel": "fileDel_427795137",
4007
4082
  "empty": "empty_427795137",
4008
4083
  "errorText": "errorText_427795137",
4009
- "moreRow": "moreRow_427795137"
4084
+ "moreRow": "moreRow_427795137",
4085
+ "credHint": "credHint_427795137",
4086
+ "credList": "credList_427795137",
4087
+ "credRow": "credRow_427795137",
4088
+ "credMain": "credMain_427795137",
4089
+ "credHost": "credHost_427795137",
4090
+ "credMeta": "credMeta_427795137",
4091
+ "credActions": "credActions_427795137",
4092
+ "credDivider": "credDivider_427795137",
4093
+ "credFormTitle": "credFormTitle_427795137",
4094
+ "credForm": "credForm_427795137",
4095
+ "credPresets": "credPresets_427795137",
4096
+ "credPresetLabel": "credPresetLabel_427795137",
4097
+ "credPreset": "credPreset_427795137",
4098
+ "credPresetOn": "credPresetOn_427795137",
4099
+ "credMethodRow": "credMethodRow_427795137",
4100
+ "credMethod": "credMethod_427795137",
4101
+ "credMethodOn": "credMethodOn_427795137",
4102
+ "credFieldHead": "credFieldHead_427795137",
4103
+ "credLink": "credLink_427795137",
4104
+ "credSecretRow": "credSecretRow_427795137",
4105
+ "credOAuth": "credOAuth_427795137",
4106
+ "credDevice": "credDevice_427795137",
4107
+ "credDeviceRow": "credDeviceRow_427795137",
4108
+ "credDeviceCode": "credDeviceCode_427795137",
4109
+ "credDeviceHint": "credDeviceHint_427795137",
4110
+ "credDeviceWaiting": "credDeviceWaiting_427795137",
4111
+ "credFooter": "credFooter_427795137",
4112
+ "credTop": "credTop_427795137",
4113
+ "credAdd": "credAdd_427795137"
4010
4114
  };
4011
4115
  //#endregion
4012
4116
  //#region src/client/panel/FloatMenu.tsx
@@ -4168,7 +4272,7 @@ window.__ModuleLoader__.load({
4168
4272
  }
4169
4273
  /** The branches section. */
4170
4274
  function BranchSection(props) {
4171
- const { t, locals, remotes, busy, onCheckout, onRename, onDelete, onCreate } = props;
4275
+ const { t, locals, remotes, busy, onCheckout, onRename, onDelete, onCreate, onManageRemotes } = props;
4172
4276
  const [menu, setMenu] = (0, react.useState)(null);
4173
4277
  const [dialog, setDialog] = (0, react.useState)(null);
4174
4278
  const [draft, setDraft] = (0, react.useState)("");
@@ -4370,6 +4474,13 @@ window.__ModuleLoader__.load({
4370
4474
  onClick: collapseAll,
4371
4475
  children: t("menuCollapseAll")
4372
4476
  }),
4477
+ onManageRemotes === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4478
+ type: "button",
4479
+ className: GitHistoryPanel_module_default.treeToggle,
4480
+ disabled: busy,
4481
+ onClick: onManageRemotes,
4482
+ children: t("panel.remotes")
4483
+ }),
4373
4484
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4374
4485
  type: "button",
4375
4486
  className: GitHistoryPanel_module_default.act,
@@ -4610,6 +4721,528 @@ window.__ModuleLoader__.load({
4610
4721
  });
4611
4722
  }
4612
4723
  //#endregion
4724
+ //#region src/client/panel/CredentialModal.tsx
4725
+ /**
4726
+ * CredentialModal: the GLOBAL git credential editor behind the topbar's
4727
+ * 「密钥配置」 button.
4728
+ *
4729
+ * Credentials are keyed by remote HOST, not by repository: one github.com
4730
+ * token serves every checkout on this machine. The list therefore never
4731
+ * shows a secret — the host routes return a mask only — and the editor
4732
+ * treats an empty secret as "keep the stored one", which is why the field's
4733
+ * placeholder changes once a host already has a credential.
4734
+ *
4735
+ * Validation is split: this component only rejects empty fields (instant
4736
+ * feedback); the host/secret shape is decided by the host half
4737
+ * (normalizeCredentialHost), whose message rides the error envelope back
4738
+ * here. Nothing in this file imports `../../credentials.ts` — that module
4739
+ * touches node:fs, which has no place in the browser bundle.
4740
+ *
4741
+ * Authentication has two paths and no password pair: device-flow hosts open
4742
+ * the browser (the token lands host-side, so the dialog only sees the mask),
4743
+ * every other host takes a pasted token. GitHub retired password auth in
4744
+ * 2021 and GitLab/Gitee expect a PAT, so a username/password form only
4745
+ * collected credentials the hosts reject.
4746
+ *
4747
+ * @module git-worktree/client/panel/CredentialModal
4748
+ */
4749
+ /** The hosts the dialog offers as one-click presets (custom hosts are typed
4750
+ * in by hand and validated host-side). */
4751
+ const PRESET_HOSTS = [
4752
+ "github.com",
4753
+ "gitlab.com",
4754
+ "gitee.com"
4755
+ ];
4756
+ /** Where each preset host mints a token. Only token-based hosts get a link:
4757
+ * a self-hosted GitLab or Gitee clone has no well-known settings page. */
4758
+ const TOKEN_PAGES = {
4759
+ "github.com": "https://github.com/settings/tokens/new?description=dsh-git-branch",
4760
+ "gitlab.com": "https://gitlab.com/-/user_settings/personal_access_tokens",
4761
+ "gitee.com": "https://gitee.com/profile/personal_access_tokens"
4762
+ };
4763
+ /** One polling interval, kept as a promise so the loop stays sequential. */
4764
+ function delay(ms) {
4765
+ return new Promise((resolve) => {
4766
+ setTimeout(resolve, ms);
4767
+ });
4768
+ }
4769
+ /** Consecutive unreachable polls before the dialog gives up. One TLS blip —
4770
+ * the reset keep-alive connection this environment sees — must not discard a
4771
+ * grant the user already approved in the browser. */
4772
+ const OAUTH_NETWORK_RETRY_LIMIT = 5;
4773
+ /** The credential editor dialog. Loads the global list on mount and after
4774
+ * every write; each write returns the fresh masked list, so no reload loop
4775
+ * (and no stale row) is possible. */
4776
+ function CredentialModal(props) {
4777
+ const { t, pushToast, onClose } = props;
4778
+ const [entries, setEntries] = (0, react.useState)([]);
4779
+ const [loaded, setLoaded] = (0, react.useState)(false);
4780
+ const [loadError, setLoadError] = (0, react.useState)();
4781
+ const [host, setHost] = (0, react.useState)("");
4782
+ const [method, setMethod] = (0, react.useState)("token");
4783
+ const [secret, setSecret] = (0, react.useState)("");
4784
+ const [seed, setSeed] = (0, react.useState)();
4785
+ const [formError, setFormError] = (0, react.useState)();
4786
+ const [busy, setBusy] = (0, react.useState)(false);
4787
+ /** Two-step delete: the first click only arms the row. */
4788
+ const [confirmHost, setConfirmHost] = (0, react.useState)();
4789
+ /** The editor starts collapsed; the "+" icon in the header row opens it,
4790
+ * and the row's 编辑 button re-opens it prefilled. Keeping the form out of
4791
+ * the DOM until it is asked for leaves the list as the modal's default. */
4792
+ const [editorOpen, setEditorOpen] = (0, react.useState)(false);
4793
+ /** The browser sign-in currently shown under the password fields. */
4794
+ const [oauth, setOauth] = (0, react.useState)({ phase: "idle" });
4795
+ /** Generation counter: one bump abandons an in-flight poll loop (cancel,
4796
+ * close, unmount), so a late answer cannot revive a closed editor. */
4797
+ const oauthRun = (0, react.useRef)(0);
4798
+ (0, react.useEffect)(() => () => {
4799
+ oauthRun.current += 1;
4800
+ }, []);
4801
+ const load = (0, react.useCallback)(async () => {
4802
+ const result = await requestCredentials();
4803
+ if (!result.ok) {
4804
+ setLoadError(result.error);
4805
+ setLoaded(true);
4806
+ return;
4807
+ }
4808
+ setEntries(result.credentials);
4809
+ setLoadError(void 0);
4810
+ setLoaded(true);
4811
+ }, []);
4812
+ (0, react.useEffect)(() => {
4813
+ load();
4814
+ }, [load]);
4815
+ const applyEditor = (0, react.useCallback)((next) => {
4816
+ oauthRun.current += 1;
4817
+ setOauth({ phase: "idle" });
4818
+ setSeed(next);
4819
+ setHost(next === void 0 ? "" : next.host);
4820
+ setMethod(next === void 0 ? "token" : next.method === "token" ? "token" : "browser");
4821
+ setSecret("");
4822
+ setFormError(void 0);
4823
+ setConfirmHost(void 0);
4824
+ }, []);
4825
+ const openNew = (0, react.useCallback)(() => {
4826
+ applyEditor(void 0);
4827
+ setEditorOpen(true);
4828
+ }, [applyEditor]);
4829
+ /** Cancel closes the form only; the modal keeps the list it was opened for. */
4830
+ const closeEditor = (0, react.useCallback)(() => {
4831
+ applyEditor(void 0);
4832
+ setEditorOpen(false);
4833
+ }, [applyEditor]);
4834
+ /** Switching path abandons whatever sign-in the other path was waiting on,
4835
+ * so a late poll answer cannot rewrite the form the user now sees. */
4836
+ const chooseMode = (0, react.useCallback)((next) => {
4837
+ if (next === method) return;
4838
+ oauthRun.current += 1;
4839
+ setOauth({ phase: "idle" });
4840
+ setMethod(next);
4841
+ }, [method]);
4842
+ const applyPreset = (0, react.useCallback)((preset) => {
4843
+ const existing = entries.find((entry) => entry.host === preset);
4844
+ if (existing !== void 0) {
4845
+ applyEditor(existing);
4846
+ return;
4847
+ }
4848
+ applyEditor(void 0);
4849
+ setHost(preset);
4850
+ setMethod(BROWSER_SIGN_IN_HOSTS.includes(preset) ? "browser" : "token");
4851
+ }, [applyEditor, entries]);
4852
+ const normalizedHost = host.trim().toLowerCase();
4853
+ const existing = entries.find((entry) => entry.host === normalizedHost);
4854
+ const tokenPage = TOKEN_PAGES[normalizedHost];
4855
+ /** Device flow is only implemented for a few hosts; the rest get the token
4856
+ * field alone, with no method switch that could promise a dead button. */
4857
+ const browserSignIn = BROWSER_SIGN_IN_HOSTS.includes(normalizedHost);
4858
+ /** The mode actually rendered: without device flow the token field wins,
4859
+ * whatever stale selection the form was carrying. */
4860
+ const authMode = browserSignIn ? method : "token";
4861
+ const save = (0, react.useCallback)(async () => {
4862
+ if (busy) return;
4863
+ const trimmedSecret = secret.trim();
4864
+ if (host.trim() === "") {
4865
+ setFormError(t("cred.hostEmpty"));
4866
+ return;
4867
+ }
4868
+ if (existing === void 0 && trimmedSecret === "") {
4869
+ setFormError(t("cred.secretEmpty"));
4870
+ return;
4871
+ }
4872
+ setBusy(true);
4873
+ const result = await requestSaveCredential({
4874
+ host: host.trim(),
4875
+ username: "",
4876
+ method: "token",
4877
+ ...trimmedSecret === "" ? {} : { secret: trimmedSecret }
4878
+ });
4879
+ setBusy(false);
4880
+ if (!result.ok) {
4881
+ setFormError(result.error);
4882
+ return;
4883
+ }
4884
+ setEntries(result.credentials);
4885
+ pushToast(t("cred.saved", { host: host.trim() }), "success");
4886
+ applyEditor(void 0);
4887
+ setEditorOpen(false);
4888
+ }, [
4889
+ applyEditor,
4890
+ busy,
4891
+ existing,
4892
+ host,
4893
+ pushToast,
4894
+ secret,
4895
+ t
4896
+ ]);
4897
+ /**
4898
+ * IDEA-style site sign-in: start a device-flow grant, open the verification
4899
+ * page, then poll until the user approves. The token is stored host-side,
4900
+ * so the dialog only ever receives the refreshed masked list.
4901
+ */
4902
+ const connect = (0, react.useCallback)(async () => {
4903
+ if (busy || oauth.phase !== "idle" || !browserSignIn) return;
4904
+ const target = normalizedHost;
4905
+ if (target === "") {
4906
+ setFormError(t("cred.hostEmpty"));
4907
+ return;
4908
+ }
4909
+ const run = ++oauthRun.current;
4910
+ setFormError(void 0);
4911
+ setOauth({ phase: "connecting" });
4912
+ const tab = window.open("", "_blank");
4913
+ const started = await requestOAuthStart({ host: target });
4914
+ if (oauthRun.current !== run) {
4915
+ tab?.close();
4916
+ return;
4917
+ }
4918
+ if (!started.ok) {
4919
+ tab?.close();
4920
+ setOauth({ phase: "idle" });
4921
+ setFormError(started.error);
4922
+ return;
4923
+ }
4924
+ const page = started.verificationUriComplete ?? started.verificationUri;
4925
+ if (tab === null || tab.closed) window.open(page, "_blank", "noopener,noreferrer");
4926
+ else {
4927
+ tab.opener = null;
4928
+ tab.location.href = page;
4929
+ }
4930
+ setOauth({
4931
+ phase: "waiting",
4932
+ userCode: started.userCode,
4933
+ verificationUri: started.verificationUri
4934
+ });
4935
+ const deadline = Date.now() + started.expiresInSeconds * 1e3;
4936
+ let intervalMs = Math.max(started.intervalSeconds, 1) * 1e3;
4937
+ let networkFailures = 0;
4938
+ while (Date.now() < deadline) {
4939
+ await delay(intervalMs);
4940
+ if (oauthRun.current !== run) return;
4941
+ const polled = await requestOAuthPoll({
4942
+ host: target,
4943
+ deviceCode: started.deviceCode
4944
+ });
4945
+ if (oauthRun.current !== run) return;
4946
+ if (!polled.ok) {
4947
+ setOauth({ phase: "idle" });
4948
+ setFormError(polled.error);
4949
+ return;
4950
+ }
4951
+ if (polled.status === "network") {
4952
+ networkFailures += 1;
4953
+ if (networkFailures >= OAUTH_NETWORK_RETRY_LIMIT) {
4954
+ setOauth({ phase: "idle" });
4955
+ setFormError(t("cred.deviceNetwork", { host: target }));
4956
+ return;
4957
+ }
4958
+ continue;
4959
+ }
4960
+ networkFailures = 0;
4961
+ if (polled.status === "pending") continue;
4962
+ if (polled.status === "slow_down") {
4963
+ intervalMs = Math.min(intervalMs * 2, 6e4);
4964
+ continue;
4965
+ }
4966
+ setEntries(polled.credentials);
4967
+ setOauth({ phase: "idle" });
4968
+ applyEditor(void 0);
4969
+ setEditorOpen(false);
4970
+ pushToast(t("cred.saved", { host: target }), "success");
4971
+ return;
4972
+ }
4973
+ if (oauthRun.current !== run) return;
4974
+ setOauth({ phase: "idle" });
4975
+ setFormError(t("cred.deviceExpired"));
4976
+ }, [
4977
+ applyEditor,
4978
+ browserSignIn,
4979
+ busy,
4980
+ normalizedHost,
4981
+ oauth.phase,
4982
+ pushToast,
4983
+ t
4984
+ ]);
4985
+ const remove = (0, react.useCallback)(async (target) => {
4986
+ if (busy) return;
4987
+ setBusy(true);
4988
+ const result = await requestDeleteCredential({ host: target });
4989
+ setBusy(false);
4990
+ if (!result.ok) {
4991
+ setFormError(result.error);
4992
+ setConfirmHost(void 0);
4993
+ return;
4994
+ }
4995
+ setEntries(result.credentials);
4996
+ pushToast(t("cred.deleted", { host: target }), "success");
4997
+ setConfirmHost(void 0);
4998
+ if (seed?.host === target) {
4999
+ applyEditor(void 0);
5000
+ setEditorOpen(false);
5001
+ }
5002
+ }, [
5003
+ applyEditor,
5004
+ busy,
5005
+ pushToast,
5006
+ seed,
5007
+ t
5008
+ ]);
5009
+ const title = seed === void 0 ? t("cred.add") : t("cred.edit");
5010
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
5011
+ open: true,
5012
+ onClose,
5013
+ title: t("cred.title"),
5014
+ closeLabel: t("cred.cancel"),
5015
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5016
+ size: "sm",
5017
+ variant: "ghost",
5018
+ onClick: onClose,
5019
+ children: t("close")
5020
+ }),
5021
+ children: [
5022
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5023
+ className: GitHistoryPanel_module_default.credTop,
5024
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5025
+ className: GitHistoryPanel_module_default.credHint,
5026
+ children: t("cred.hint")
5027
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5028
+ type: "button",
5029
+ className: GitHistoryPanel_module_default.credAdd,
5030
+ title: t("cred.add"),
5031
+ "aria-label": t("cred.add"),
5032
+ "aria-expanded": editorOpen,
5033
+ onClick: openNew,
5034
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5035
+ "aria-hidden": "true",
5036
+ children: "+"
5037
+ })
5038
+ })]
5039
+ }),
5040
+ loadError !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5041
+ className: GitHistoryPanel_module_default.formError,
5042
+ children: loadError
5043
+ }) : null,
5044
+ loaded && loadError === void 0 && entries.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5045
+ className: GitHistoryPanel_module_default.empty,
5046
+ children: t("cred.empty")
5047
+ }) : null,
5048
+ entries.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5049
+ className: GitHistoryPanel_module_default.credList,
5050
+ children: entries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5051
+ className: GitHistoryPanel_module_default.credRow,
5052
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5053
+ className: GitHistoryPanel_module_default.credMain,
5054
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5055
+ className: GitHistoryPanel_module_default.credHost,
5056
+ children: entry.host
5057
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5058
+ className: GitHistoryPanel_module_default.credMeta,
5059
+ children: [
5060
+ entry.username === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [entry.username, " · "] }),
5061
+ entry.method === "token" ? t("cred.methodToken") : t("cred.methodPassword"),
5062
+ " · ",
5063
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: entry.secretMask })
5064
+ ]
5065
+ })]
5066
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5067
+ className: GitHistoryPanel_module_default.credActions,
5068
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5069
+ size: "sm",
5070
+ variant: "ghost",
5071
+ onClick: () => {
5072
+ applyEditor(entry);
5073
+ setEditorOpen(true);
5074
+ },
5075
+ children: t("cred.edit")
5076
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5077
+ size: "sm",
5078
+ variant: "ghost",
5079
+ disabled: busy,
5080
+ onClick: () => confirmHost === entry.host ? void remove(entry.host) : setConfirmHost(entry.host),
5081
+ children: confirmHost === entry.host ? t("cred.confirmDelete") : t("cred.delete")
5082
+ })]
5083
+ })]
5084
+ }, entry.host))
5085
+ }) : null,
5086
+ editorOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
5087
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: GitHistoryPanel_module_default.credDivider }),
5088
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5089
+ className: GitHistoryPanel_module_default.credFormTitle,
5090
+ children: title
5091
+ }),
5092
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5093
+ className: GitHistoryPanel_module_default.credForm,
5094
+ children: [
5095
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5096
+ className: GitHistoryPanel_module_default.credPresets,
5097
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5098
+ className: GitHistoryPanel_module_default.credPresetLabel,
5099
+ children: t("cred.presets")
5100
+ }), PRESET_HOSTS.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5101
+ type: "button",
5102
+ className: normalizedHost === preset ? GitHistoryPanel_module_default.credPresetOn : GitHistoryPanel_module_default.credPreset,
5103
+ onClick: () => applyPreset(preset),
5104
+ children: preset
5105
+ }, preset))]
5106
+ }),
5107
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5108
+ className: GitHistoryPanel_module_default.formField,
5109
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
5110
+ className: GitHistoryPanel_module_default.formLabel,
5111
+ htmlFor: "git-credential-host",
5112
+ children: t("cred.hostLabel")
5113
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5114
+ id: "git-credential-host",
5115
+ className: GitHistoryPanel_module_default.formInput,
5116
+ value: host,
5117
+ placeholder: t("cred.hostPlaceholder"),
5118
+ "data-modal-autofocus": seed === void 0 ? true : void 0,
5119
+ onChange: (event) => setHost(event.target.value)
5120
+ })]
5121
+ }),
5122
+ browserSignIn ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5123
+ className: GitHistoryPanel_module_default.formField,
5124
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5125
+ className: GitHistoryPanel_module_default.formLabel,
5126
+ children: t("cred.method")
5127
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5128
+ className: GitHistoryPanel_module_default.credMethodRow,
5129
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5130
+ type: "button",
5131
+ "aria-pressed": authMode === "browser",
5132
+ className: authMode === "browser" ? GitHistoryPanel_module_default.credMethodOn : GitHistoryPanel_module_default.credMethod,
5133
+ onClick: () => chooseMode("browser"),
5134
+ children: t("cred.methodBrowser")
5135
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5136
+ type: "button",
5137
+ "aria-pressed": authMode === "token",
5138
+ className: authMode === "token" ? GitHistoryPanel_module_default.credMethodOn : GitHistoryPanel_module_default.credMethod,
5139
+ onClick: () => chooseMode("token"),
5140
+ children: t("cred.methodToken")
5141
+ })]
5142
+ })]
5143
+ }) : null,
5144
+ authMode === "token" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5145
+ className: GitHistoryPanel_module_default.formField,
5146
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5147
+ className: GitHistoryPanel_module_default.credFieldHead,
5148
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
5149
+ className: GitHistoryPanel_module_default.formLabel,
5150
+ htmlFor: "git-credential-secret",
5151
+ children: t("cred.secretTokenLabel")
5152
+ })
5153
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5154
+ className: GitHistoryPanel_module_default.credSecretRow,
5155
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5156
+ id: "git-credential-secret",
5157
+ className: GitHistoryPanel_module_default.formInput,
5158
+ type: "password",
5159
+ value: secret,
5160
+ placeholder: existing === void 0 ? t("cred.secretPlaceholder") : t("cred.secretKeep"),
5161
+ onChange: (event) => setSecret(event.target.value),
5162
+ onKeyDown: (event) => {
5163
+ if (event.key !== "Enter") return;
5164
+ event.preventDefault();
5165
+ save();
5166
+ }
5167
+ }), tokenPage !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5168
+ size: "sm",
5169
+ variant: "outline",
5170
+ onClick: () => window.open(tokenPage, "_blank", "noopener,noreferrer"),
5171
+ children: t("cred.createToken")
5172
+ }) : null]
5173
+ })]
5174
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5175
+ className: GitHistoryPanel_module_default.credOAuth,
5176
+ children: [
5177
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5178
+ className: GitHistoryPanel_module_default.credDeviceHint,
5179
+ children: t("cred.browserHint")
5180
+ }),
5181
+ oauth.phase === "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5182
+ size: "sm",
5183
+ variant: "primary",
5184
+ disabled: busy,
5185
+ onClick: () => void connect(),
5186
+ children: t("cred.connect")
5187
+ }) : null,
5188
+ oauth.phase === "connecting" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5189
+ className: GitHistoryPanel_module_default.credDeviceHint,
5190
+ children: t("cred.connecting")
5191
+ }) : null,
5192
+ oauth.phase === "waiting" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5193
+ className: GitHistoryPanel_module_default.credDevice,
5194
+ children: [
5195
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5196
+ className: GitHistoryPanel_module_default.credDeviceRow,
5197
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5198
+ className: GitHistoryPanel_module_default.credDeviceCode,
5199
+ children: oauth.userCode ?? ""
5200
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
5201
+ className: GitHistoryPanel_module_default.credLink,
5202
+ href: oauth.verificationUri,
5203
+ target: "_blank",
5204
+ rel: "noreferrer",
5205
+ children: oauth.verificationUri
5206
+ })]
5207
+ }),
5208
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5209
+ className: GitHistoryPanel_module_default.credDeviceHint,
5210
+ children: t("cred.deviceHint")
5211
+ }),
5212
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5213
+ className: GitHistoryPanel_module_default.credDeviceWaiting,
5214
+ children: t("cred.deviceWaiting")
5215
+ })
5216
+ ]
5217
+ }) : null
5218
+ ]
5219
+ }),
5220
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5221
+ className: GitHistoryPanel_module_default.formError,
5222
+ children: formError ?? ""
5223
+ })
5224
+ ]
5225
+ }),
5226
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5227
+ className: GitHistoryPanel_module_default.credFooter,
5228
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5229
+ size: "sm",
5230
+ variant: "outline",
5231
+ onClick: closeEditor,
5232
+ children: t("cancel")
5233
+ }), authMode === "token" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5234
+ size: "sm",
5235
+ variant: "primary",
5236
+ disabled: busy,
5237
+ onClick: () => void save(),
5238
+ children: busy ? t("cred.saving") : t("cred.save")
5239
+ }) : null]
5240
+ })
5241
+ ] }) : null
5242
+ ]
5243
+ });
5244
+ }
5245
+ //#endregion
4613
5246
  //#region src/client/git-graph.ts
4614
5247
  /** Lazily extend `lanes` so `index` exists. */
4615
5248
  function ensureLane(lanes, index) {
@@ -5279,6 +5912,286 @@ window.__ModuleLoader__.load({
5279
5912
  });
5280
5913
  }
5281
5914
  //#endregion
5915
+ //#region src/client/panel/RemotesModal.tsx
5916
+ /**
5917
+ * RemotesModal: the repository's remote-address editor (behind the branch
5918
+ * section's 「管理远程」 button).
5919
+ *
5920
+ * A remote belongs to the REPOSITORY, not to a worktree, so this dialog
5921
+ * mutates `.git/config` through the plugin's remote routes and then reports
5922
+ * the fresh list back to the caller — the panel reloads its branch rows so
5923
+ * a new remote's branches show up without a manual refresh.
5924
+ *
5925
+ * Every mutation returns the complete list (the host half re-reads
5926
+ * `git remote -v` after writing), so the dialog never guesses what changed.
5927
+ * Names are immutable here: `git remote rename` also rewrites tracking
5928
+ * config, which this panel does not offer; the URL is the mutable part.
5929
+ *
5930
+ * The dialog follows the credential editor's interaction shape, so the two
5931
+ * modals read as one design: the list is the default view, a 「+」 in the
5932
+ * header row opens the editor, and the row's edit button re-opens it
5933
+ * prefilled. Cancel closes the editor only — the modal itself stays open.
5934
+ *
5935
+ * @module git-worktree/client/panel/RemotesModal
5936
+ */
5937
+ /** The remote editor dialog. */
5938
+ function RemotesModal(props) {
5939
+ const { repoPath, t, pushToast, onClose, onChanged } = props;
5940
+ const [remotes, setRemotes] = (0, react.useState)([]);
5941
+ const [loaded, setLoaded] = (0, react.useState)(false);
5942
+ const [loadError, setLoadError] = (0, react.useState)();
5943
+ const [name, setName] = (0, react.useState)("");
5944
+ const [url, setUrl] = (0, react.useState)("");
5945
+ /** The remote whose URL is being rewritten (undefined = adding a new one). */
5946
+ const [editing, setEditing] = (0, react.useState)();
5947
+ const [formError, setFormError] = (0, react.useState)();
5948
+ const [busy, setBusy] = (0, react.useState)(false);
5949
+ /** Two-step remove: the first click only arms the row. */
5950
+ const [confirmName, setConfirmName] = (0, react.useState)();
5951
+ /** The editor starts collapsed; the 「+」 in the header row opens it, and
5952
+ * the row's 修改地址 button re-opens it prefilled. Keeping the form out of
5953
+ * the DOM until it is asked for leaves the list as the modal's default —
5954
+ * the same shape the credential editor uses. */
5955
+ const [editorOpen, setEditorOpen] = (0, react.useState)(false);
5956
+ const load = (0, react.useCallback)(async () => {
5957
+ const result = await requestRemotes(repoPath);
5958
+ if (!result.ok) {
5959
+ setLoadError(result.error);
5960
+ setLoaded(true);
5961
+ return;
5962
+ }
5963
+ setRemotes(result.remotes);
5964
+ setLoadError(void 0);
5965
+ setLoaded(true);
5966
+ }, [repoPath]);
5967
+ (0, react.useEffect)(() => {
5968
+ load();
5969
+ }, [load]);
5970
+ const resetEditor = (0, react.useCallback)(() => {
5971
+ setEditing(void 0);
5972
+ setName("");
5973
+ setUrl("");
5974
+ setFormError(void 0);
5975
+ setConfirmName(void 0);
5976
+ setEditorOpen(false);
5977
+ }, []);
5978
+ /** The 「+」 button: a fresh, empty editor for a new remote. */
5979
+ const openNew = (0, react.useCallback)(() => {
5980
+ resetEditor();
5981
+ setEditorOpen(true);
5982
+ }, [resetEditor]);
5983
+ const startEdit = (0, react.useCallback)((entry) => {
5984
+ setEditing(entry.name);
5985
+ setName(entry.name);
5986
+ setUrl(entry.fetchUrl);
5987
+ setFormError(void 0);
5988
+ setConfirmName(void 0);
5989
+ setEditorOpen(true);
5990
+ }, []);
5991
+ const submit = (0, react.useCallback)(async () => {
5992
+ if (busy) return;
5993
+ if (name.trim() === "") {
5994
+ setFormError(t("remotes.nameEmpty"));
5995
+ return;
5996
+ }
5997
+ if (url.trim() === "") {
5998
+ setFormError(t("remotes.urlEmpty"));
5999
+ return;
6000
+ }
6001
+ setBusy(true);
6002
+ const result = await requestMutateRemote({
6003
+ repoPath,
6004
+ action: editing === void 0 ? "add" : "set-url",
6005
+ name: name.trim(),
6006
+ url: url.trim()
6007
+ });
6008
+ setBusy(false);
6009
+ if (!result.ok) {
6010
+ setFormError(result.error);
6011
+ return;
6012
+ }
6013
+ setRemotes(result.remotes);
6014
+ pushToast(t("remotes.saved", { name: name.trim() }), "success");
6015
+ resetEditor();
6016
+ onChanged?.();
6017
+ }, [
6018
+ busy,
6019
+ editing,
6020
+ name,
6021
+ onChanged,
6022
+ pushToast,
6023
+ repoPath,
6024
+ resetEditor,
6025
+ t,
6026
+ url
6027
+ ]);
6028
+ const remove = (0, react.useCallback)(async (target) => {
6029
+ if (busy) return;
6030
+ setBusy(true);
6031
+ const result = await requestMutateRemote({
6032
+ repoPath,
6033
+ action: "remove",
6034
+ name: target
6035
+ });
6036
+ setBusy(false);
6037
+ if (!result.ok) {
6038
+ setFormError(result.error);
6039
+ setConfirmName(void 0);
6040
+ return;
6041
+ }
6042
+ setRemotes(result.remotes);
6043
+ pushToast(t("remotes.removed", { name: target }), "success");
6044
+ setConfirmName(void 0);
6045
+ if (editing === target) resetEditor();
6046
+ onChanged?.();
6047
+ }, [
6048
+ busy,
6049
+ editing,
6050
+ onChanged,
6051
+ pushToast,
6052
+ repoPath,
6053
+ resetEditor,
6054
+ t
6055
+ ]);
6056
+ const editorTitle = editing === void 0 ? t("remotes.add") : `${t("remotes.edit")} · ${editing}`;
6057
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
6058
+ open: true,
6059
+ onClose,
6060
+ title: t("remotes.title"),
6061
+ closeLabel: t("remotes.cancel"),
6062
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
6063
+ size: "sm",
6064
+ variant: "ghost",
6065
+ onClick: onClose,
6066
+ children: t("close")
6067
+ }),
6068
+ children: [
6069
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6070
+ className: GitHistoryPanel_module_default.credTop,
6071
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6072
+ className: GitHistoryPanel_module_default.credHint,
6073
+ children: t("remotes.hint")
6074
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6075
+ type: "button",
6076
+ className: GitHistoryPanel_module_default.credAdd,
6077
+ title: t("remotes.add"),
6078
+ "aria-label": t("remotes.add"),
6079
+ "aria-expanded": editorOpen,
6080
+ onClick: openNew,
6081
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6082
+ "aria-hidden": "true",
6083
+ children: "+"
6084
+ })
6085
+ })]
6086
+ }),
6087
+ loadError !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6088
+ className: GitHistoryPanel_module_default.formError,
6089
+ children: loadError
6090
+ }) : null,
6091
+ loaded && loadError === void 0 && remotes.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6092
+ className: GitHistoryPanel_module_default.empty,
6093
+ children: t("remotes.empty")
6094
+ }) : null,
6095
+ remotes.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6096
+ className: GitHistoryPanel_module_default.credList,
6097
+ children: remotes.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6098
+ className: GitHistoryPanel_module_default.credRow,
6099
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6100
+ className: GitHistoryPanel_module_default.credMain,
6101
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6102
+ className: GitHistoryPanel_module_default.credHost,
6103
+ children: entry.name
6104
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6105
+ className: GitHistoryPanel_module_default.credMeta,
6106
+ children: entry.fetchUrl === entry.pushUrl ? entry.fetchUrl : `${t("remotes.fetchUrl")}: ${entry.fetchUrl} · ${t("remotes.pushUrl")}: ${entry.pushUrl}`
6107
+ })]
6108
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6109
+ className: GitHistoryPanel_module_default.credActions,
6110
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
6111
+ size: "sm",
6112
+ variant: "ghost",
6113
+ onClick: () => startEdit(entry),
6114
+ children: t("remotes.edit")
6115
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
6116
+ size: "sm",
6117
+ variant: "ghost",
6118
+ disabled: busy,
6119
+ onClick: () => confirmName === entry.name ? void remove(entry.name) : setConfirmName(entry.name),
6120
+ children: confirmName === entry.name ? t("remotes.confirmRemove") : t("remotes.remove")
6121
+ })]
6122
+ })]
6123
+ }, entry.name))
6124
+ }) : null,
6125
+ editorOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
6126
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: GitHistoryPanel_module_default.credDivider }),
6127
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6128
+ className: GitHistoryPanel_module_default.credFormTitle,
6129
+ children: editorTitle
6130
+ }),
6131
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6132
+ className: GitHistoryPanel_module_default.credForm,
6133
+ children: [
6134
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6135
+ className: GitHistoryPanel_module_default.formField,
6136
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
6137
+ className: GitHistoryPanel_module_default.formLabel,
6138
+ htmlFor: "git-remote-name",
6139
+ children: t("remotes.nameLabel")
6140
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6141
+ id: "git-remote-name",
6142
+ className: GitHistoryPanel_module_default.formInput,
6143
+ value: name,
6144
+ placeholder: t("remotes.namePlaceholder"),
6145
+ readOnly: editing !== void 0,
6146
+ "data-modal-autofocus": editing === void 0 ? true : void 0,
6147
+ onChange: (event) => setName(event.target.value)
6148
+ })]
6149
+ }),
6150
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6151
+ className: GitHistoryPanel_module_default.formField,
6152
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
6153
+ className: GitHistoryPanel_module_default.formLabel,
6154
+ htmlFor: "git-remote-url",
6155
+ children: t("remotes.urlLabel")
6156
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6157
+ id: "git-remote-url",
6158
+ className: GitHistoryPanel_module_default.formInput,
6159
+ value: url,
6160
+ placeholder: t("remotes.urlPlaceholder"),
6161
+ onChange: (event) => setUrl(event.target.value),
6162
+ onKeyDown: (event) => {
6163
+ if (event.key !== "Enter") return;
6164
+ event.preventDefault();
6165
+ submit();
6166
+ }
6167
+ })]
6168
+ }),
6169
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6170
+ className: GitHistoryPanel_module_default.formError,
6171
+ children: formError ?? ""
6172
+ })
6173
+ ]
6174
+ }),
6175
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6176
+ className: GitHistoryPanel_module_default.credFooter,
6177
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
6178
+ size: "sm",
6179
+ variant: "outline",
6180
+ onClick: resetEditor,
6181
+ children: t("cancel")
6182
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
6183
+ size: "sm",
6184
+ variant: "primary",
6185
+ disabled: busy,
6186
+ onClick: () => void submit(),
6187
+ children: busy ? t("remotes.saving") : t("remotes.save")
6188
+ })]
6189
+ })
6190
+ ] }) : null
6191
+ ]
6192
+ });
6193
+ }
6194
+ //#endregion
5282
6195
  //#region src/client/panel/UncommittedSection.tsx
5283
6196
  /**
5284
6197
  * UncommittedSection: the panel's first block — the staged/unstaged file
@@ -5580,6 +6493,8 @@ window.__ModuleLoader__.load({
5580
6493
  const [expanded, setExpanded] = (0, react.useState)(null);
5581
6494
  const [filesByHash, setFilesByHash] = (0, react.useState)({});
5582
6495
  const [sectionBusy, setSectionBusy] = (0, react.useState)(false);
6496
+ /** The remote-address editor (branch card's 「管理远程」 entry). */
6497
+ const [remotesOpen, setRemotesOpen] = (0, react.useState)(false);
5583
6498
  const [syncBusy, setSyncBusy] = (0, react.useState)(false);
5584
6499
  const [commitMenuOpen, setCommitMenuOpen] = (0, react.useState)(false);
5585
6500
  const [commitDraft, setCommitDraft] = (0, react.useState)("");
@@ -5638,21 +6553,28 @@ window.__ModuleLoader__.load({
5638
6553
  (0, react.useEffect)(() => onGitMutated(refresh), [refresh]);
5639
6554
  (0, react.useImperativeHandle)(ref, () => ({ refresh }), [refresh]);
5640
6555
  const repo = status !== null && status.repo ? status : null;
5641
- /** Run one mutating verb with the section-busy guard and error toast.
6556
+ /** Run one mutating verb with the error toast and the cross-surface notice.
5642
6557
  * Mutations refresh through `loadAll` so their re-read bumps the request
5643
6558
  * sequence — an older in-flight snapshot can never land over the fresh
5644
6559
  * post-mutation state. A SUCCESS also emits the cross-surface notice so
5645
6560
  * the composer chip drops the branch git just left (stage toggles ride
5646
- * along — one extra idempotent /status beats per-verb wiring). */
5647
- const runGuarded = (0, react.useCallback)(async (verb) => {
5648
- setSectionBusy(true);
6561
+ * along — one extra idempotent /status beats per-verb wiring).
6562
+ *
6563
+ * `scope` names what the verb touches: `'section'` (default) claims the
6564
+ * working-tree busy gate — the commit box's primary button reads
6565
+ * 「提交中…」 while it is on. A tag verb (create or push) passes `'repo'`:
6566
+ * it neither reads nor writes the working tree, so lighting that gate
6567
+ * would report a commit that is not happening. */
6568
+ const runGuarded = (0, react.useCallback)(async (verb, scope = "section") => {
6569
+ const section = scope === "section";
6570
+ if (section) setSectionBusy(true);
5649
6571
  try {
5650
6572
  await verb();
5651
6573
  emitGitMutated();
5652
6574
  } catch (error) {
5653
6575
  pushToast(t("errorGeneric", { message: failureText(error) }), "error");
5654
6576
  } finally {
5655
- setSectionBusy(false);
6577
+ if (section) setSectionBusy(false);
5656
6578
  }
5657
6579
  }, [pushToast, t]);
5658
6580
  const onToggleRow = (0, react.useCallback)((row) => {
@@ -5894,14 +6816,16 @@ window.__ModuleLoader__.load({
5894
6816
  ]);
5895
6817
  /** Tag one commit. Runs against the repository (a tag is a repository-wide
5896
6818
  * ref), so the history card can tag a commit that no checkout has. Only
5897
- * local: pushing tags is a release step, not a side effect of a right-click. */
6819
+ * local: pushing tags is a release step, not a side effect of a right-click.
6820
+ * `'repo'`: a tag write leaves the working tree untouched — it must not
6821
+ * turn the commit box's primary button into 「提交中…」. */
5898
6822
  const onCreateTag = (0, react.useCallback)((name, target, message) => {
5899
6823
  runGuarded(async () => {
5900
6824
  const result = await requestCreateTag(cwd, name, target, message);
5901
6825
  if (!result.ok) throw new Error(result.error);
5902
6826
  pushToast(t("panel.tagDone", { tag: result.tag }), "success");
5903
6827
  await loadAll();
5904
- });
6828
+ }, "repo");
5905
6829
  }, [
5906
6830
  cwd,
5907
6831
  loadAll,
@@ -5924,7 +6848,7 @@ window.__ModuleLoader__.load({
5924
6848
  tag: result.tag,
5925
6849
  remote: result.remote
5926
6850
  }), "success");
5927
- });
6851
+ }, "repo");
5928
6852
  }, [
5929
6853
  cwd,
5930
6854
  pushToast,
@@ -6041,7 +6965,8 @@ window.__ModuleLoader__.load({
6041
6965
  onCheckout,
6042
6966
  onRename,
6043
6967
  onDelete,
6044
- onCreate
6968
+ onCreate,
6969
+ onManageRemotes: () => setRemotesOpen(true)
6045
6970
  }),
6046
6971
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HistorySection, {
6047
6972
  t,
@@ -6063,7 +6988,14 @@ window.__ModuleLoader__.load({
6063
6988
  onCreateTag,
6064
6989
  onPushTag,
6065
6990
  pushToast
6066
- })
6991
+ }),
6992
+ remotesOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RemotesModal, {
6993
+ repoPath: cwd,
6994
+ t,
6995
+ pushToast,
6996
+ onClose: () => setRemotesOpen(false),
6997
+ onChanged: () => void loadAll()
6998
+ }) : null
6067
6999
  ]
6068
7000
  });
6069
7001
  });
@@ -6091,6 +7023,7 @@ window.__ModuleLoader__.load({
6091
7023
  }, []);
6092
7024
  const accelState = (0, react.useSyncExternalStore)(accel.store.subscribe, accel.store.getSnapshot);
6093
7025
  const [accelOpen, setAccelOpen] = (0, react.useState)(false);
7026
+ const [credOpen, setCredOpen] = (0, react.useState)(false);
6094
7027
  const accelOn = (0, react.useCallback)(() => accel.store.getSnapshot().enabled, [accel]);
6095
7028
  const closeAccel = (0, react.useCallback)(() => {
6096
7029
  accel.discardAccel();
@@ -6109,46 +7042,66 @@ window.__ModuleLoader__.load({
6109
7042
  })
6110
7043
  }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6111
7044
  className: GitHistoryPanel_module_default.topbarActions,
6112
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6113
- className: GitHistoryPanel_module_default.cnWrap,
6114
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7045
+ children: [
7046
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7047
+ className: GitHistoryPanel_module_default.cnWrap,
7048
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7049
+ type: "button",
7050
+ className: accelState.enabled ? GitHistoryPanel_module_default.cnBtnOn : GitHistoryPanel_module_default.cnBtn,
7051
+ title: t("panel.cnAccelerate"),
7052
+ "aria-label": t("panel.cnAccelerate"),
7053
+ "aria-expanded": accelOpen,
7054
+ onClick: () => accelOpen ? closeAccel() : setAccelOpen(true),
7055
+ children: [
7056
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7057
+ "aria-hidden": "true",
7058
+ children: "⚡"
7059
+ }),
7060
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("panel.cnAccelerate") }),
7061
+ accelState.enabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7062
+ className: GitHistoryPanel_module_default.cnDot,
7063
+ "aria-hidden": "true"
7064
+ }) : null
7065
+ ]
7066
+ }), accelOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CnAcceleratePopover, {
7067
+ accel,
7068
+ t,
7069
+ pushToast,
7070
+ onClose: closeAccel
7071
+ }) : null]
7072
+ }),
7073
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
6115
7074
  type: "button",
6116
- className: accelState.enabled ? GitHistoryPanel_module_default.cnBtnOn : GitHistoryPanel_module_default.cnBtn,
6117
- title: t("panel.cnAccelerate"),
6118
- "aria-label": t("panel.cnAccelerate"),
6119
- "aria-expanded": accelOpen,
6120
- onClick: () => accelOpen ? closeAccel() : setAccelOpen(true),
6121
- children: [
6122
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6123
- "aria-hidden": "true",
6124
- children: "⚡"
6125
- }),
6126
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("panel.cnAccelerate") }),
6127
- accelState.enabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6128
- className: GitHistoryPanel_module_default.cnDot,
6129
- "aria-hidden": "true"
6130
- }) : null
6131
- ]
6132
- }), accelOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CnAcceleratePopover, {
6133
- accel,
6134
- t,
6135
- pushToast,
6136
- onClose: closeAccel
6137
- }) : null]
6138
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6139
- type: "button",
6140
- className: GitHistoryPanel_module_default.refreshBtn,
6141
- disabled: cwd === void 0,
6142
- title: t("panel.refresh"),
6143
- "aria-label": t("panel.refresh"),
6144
- onClick: () => dataRef.current?.refresh(),
6145
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutlineRegular, {
6146
- size: 14,
6147
- "aria-hidden": "true"
7075
+ className: credOpen ? GitHistoryPanel_module_default.cnBtnOn : GitHistoryPanel_module_default.cnBtn,
7076
+ title: t("panel.credentials"),
7077
+ "aria-label": t("panel.credentials"),
7078
+ "aria-expanded": credOpen,
7079
+ onClick: () => setCredOpen((open) => !open),
7080
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7081
+ "aria-hidden": "true",
7082
+ children: "🔑"
7083
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("panel.credentials") })]
7084
+ }),
7085
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7086
+ type: "button",
7087
+ className: GitHistoryPanel_module_default.refreshBtn,
7088
+ disabled: cwd === void 0,
7089
+ title: t("panel.refresh"),
7090
+ "aria-label": t("panel.refresh"),
7091
+ onClick: () => dataRef.current?.refresh(),
7092
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutlineRegular, {
7093
+ size: 14,
7094
+ "aria-hidden": "true"
7095
+ })
6148
7096
  })
6149
- })]
7097
+ ]
6150
7098
  })]
6151
7099
  }),
7100
+ credOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CredentialModal, {
7101
+ t,
7102
+ pushToast,
7103
+ onClose: () => setCredOpen(false)
7104
+ }) : null,
6152
7105
  cwd === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6153
7106
  className: GitHistoryPanel_module_default.empty,
6154
7107
  children: t("panel.noSession")
@@ -7476,7 +8429,72 @@ window.__ModuleLoader__.load({
7476
8429
  "accel.saveFailed": "The change did not save. Try again.",
7477
8430
  "accel.savedOn": "China acceleration enabled: {url}",
7478
8431
  "accel.savedOff": "China acceleration disabled",
7479
- "accel.mirrorDownHint": "If the mirror is down, disable acceleration or change the URL"
8432
+ "accel.mirrorDownHint": "If the mirror is down, disable acceleration or change the URL",
8433
+ "panel.credentials": "Credentials",
8434
+ "cred.title": "Git credentials",
8435
+ "cred.hint": "Global, one entry per host: a token saved for github.com serves every repository. Stored only on this machine in git-credentials.json.",
8436
+ "cred.empty": "No credentials saved yet",
8437
+ "cred.hostLabel": "Host",
8438
+ "cred.hostPlaceholder": "github.com",
8439
+ "cred.presets": "Common hosts",
8440
+ "cred.customPreset": "Custom host",
8441
+ "cred.secretTokenLabel": "Token",
8442
+ "cred.secretKeep": "A secret is already saved — leave blank to keep it",
8443
+ "cred.secretPlaceholder": "Paste a personal access token",
8444
+ "cred.method": "Authentication",
8445
+ "cred.methodToken": "Token",
8446
+ "cred.methodBrowser": "Browser sign-in",
8447
+ "cred.methodPassword": "Password",
8448
+ "cred.getToken": "Get a token",
8449
+ "cred.add": "Add credential",
8450
+ "cred.edit": "Edit",
8451
+ "cred.save": "Save",
8452
+ "cred.saving": "Saving…",
8453
+ "cred.delete": "Delete",
8454
+ "cred.deleting": "Deleting…",
8455
+ "cred.confirmDelete": "Delete for good",
8456
+ "cred.cancel": "Cancel",
8457
+ "cred.saved": "Saved the credential for {host}",
8458
+ "cred.deleted": "Deleted the credential for {host}",
8459
+ "cred.loadFailed": "Could not read the credential store",
8460
+ "cred.hostInvalid": "Enter a host such as github.com",
8461
+ "cred.hostEmpty": "Enter a host",
8462
+ "cred.secretEmpty": "Enter a token for a new host",
8463
+ "cred.connect": "Sign in with browser",
8464
+ "cred.connecting": "Signing in…",
8465
+ "cred.browserHint": "Approve access in the browser window; the token is saved for this host automatically.",
8466
+ "cred.deviceCode": "Verification code",
8467
+ "cred.deviceHint": "Enter this code in the browser window that just opened",
8468
+ "cred.deviceWaiting": "Waiting for approval…",
8469
+ "cred.deviceExpired": "The code expired — start the sign-in again",
8470
+ "cred.deviceDenied": "The sign-in was denied",
8471
+ "cred.deviceFailed": "The browser sign-in failed: {error}",
8472
+ "cred.deviceNetwork": "Could not reach {host} — check your network or proxy and try again",
8473
+ "cred.createToken": "Create",
8474
+ "panel.remotes": "Remotes",
8475
+ "remotes.title": "Remote addresses",
8476
+ "remotes.hint": "Remotes belong to the whole repository and apply to every worktree of it.",
8477
+ "remotes.empty": "No remote is configured",
8478
+ "remotes.add": "Add remote",
8479
+ "remotes.nameLabel": "Name",
8480
+ "remotes.namePlaceholder": "origin",
8481
+ "remotes.urlLabel": "Address",
8482
+ "remotes.urlPlaceholder": "https://github.com/user/repo.git",
8483
+ "remotes.fetchUrl": "Fetch",
8484
+ "remotes.pushUrl": "Push",
8485
+ "remotes.save": "Save",
8486
+ "remotes.saving": "Saving…",
8487
+ "remotes.edit": "Change address",
8488
+ "remotes.remove": "Remove",
8489
+ "remotes.confirmRemove": "Remove for good",
8490
+ "remotes.cancel": "Cancel",
8491
+ "remotes.saved": "Updated the remote {name}",
8492
+ "remotes.removed": "Removed the remote {name}",
8493
+ "remotes.loadFailed": "Could not read the remotes",
8494
+ "remotes.nameEmpty": "Enter a name",
8495
+ "remotes.nameInvalid": "Only letters, digits, \".\", \"_\" and \"-\" are allowed",
8496
+ "remotes.urlEmpty": "Enter an address",
8497
+ "remotes.urlInvalid": "Enter a valid remote address"
7480
8498
  };
7481
8499
  /** 中文词典。 */
7482
8500
  const zh = {
@@ -7697,7 +8715,72 @@ window.__ModuleLoader__.load({
7697
8715
  "accel.saveFailed": "保存未生效,请重试。",
7698
8716
  "accel.savedOn": "已启用国内加速:{url}",
7699
8717
  "accel.savedOff": "已关闭国内加速",
7700
- "accel.mirrorDownHint": "若加速地址不可用,可停用国内加速或更换地址"
8718
+ "accel.mirrorDownHint": "若加速地址不可用,可停用国内加速或更换地址",
8719
+ "panel.credentials": "密钥配置",
8720
+ "cred.title": "Git 密钥配置",
8721
+ "cred.hint": "全局生效:同一域名只需配置一次,该域名的所有仓库共用。密钥只保存在本机 git-credentials.json 中。",
8722
+ "cred.empty": "还没有配置任何密钥",
8723
+ "cred.hostLabel": "域名",
8724
+ "cred.hostPlaceholder": "github.com",
8725
+ "cred.presets": "常用平台",
8726
+ "cred.customPreset": "自定义域名",
8727
+ "cred.secretTokenLabel": "令牌",
8728
+ "cred.secretKeep": "已保存密钥,留空表示不修改",
8729
+ "cred.secretPlaceholder": "粘贴个人访问令牌",
8730
+ "cred.method": "认证方式",
8731
+ "cred.methodToken": "令牌",
8732
+ "cred.methodBrowser": "浏览器登录",
8733
+ "cred.methodPassword": "密码",
8734
+ "cred.getToken": "获取令牌",
8735
+ "cred.add": "新增密钥",
8736
+ "cred.edit": "编辑",
8737
+ "cred.save": "保存",
8738
+ "cred.saving": "保存中…",
8739
+ "cred.delete": "删除",
8740
+ "cred.deleting": "删除中…",
8741
+ "cred.confirmDelete": "确认删除",
8742
+ "cred.cancel": "取消",
8743
+ "cred.saved": "已保存 {host} 的密钥",
8744
+ "cred.deleted": "已删除 {host} 的密钥",
8745
+ "cred.loadFailed": "读取密钥失败",
8746
+ "cred.hostInvalid": "请输入类似 github.com 的域名",
8747
+ "cred.hostEmpty": "请输入域名",
8748
+ "cred.secretEmpty": "新域名必须填写令牌",
8749
+ "cred.connect": "点击跳转登录",
8750
+ "cred.connecting": "正在跳转…",
8751
+ "cred.browserHint": "在浏览器窗口中完成授权,成功后会自动保存该域名的令牌。",
8752
+ "cred.deviceCode": "验证码",
8753
+ "cred.deviceHint": "在刚打开的浏览器窗口输入此验证码",
8754
+ "cred.deviceWaiting": "等待授权中…",
8755
+ "cred.deviceExpired": "验证码已过期,请重新登录",
8756
+ "cred.deviceDenied": "授权已被拒绝",
8757
+ "cred.deviceFailed": "浏览器授权失败:{error}",
8758
+ "cred.deviceNetwork": "无法连接 {host},请检查网络或代理后重试",
8759
+ "cred.createToken": "创建",
8760
+ "panel.remotes": "管理远程",
8761
+ "remotes.title": "远程地址",
8762
+ "remotes.hint": "远程地址属于整个仓库,对该仓库的所有工作树生效。",
8763
+ "remotes.empty": "还没有配置远程地址",
8764
+ "remotes.add": "新增远程",
8765
+ "remotes.nameLabel": "名称",
8766
+ "remotes.namePlaceholder": "origin",
8767
+ "remotes.urlLabel": "地址",
8768
+ "remotes.urlPlaceholder": "https://github.com/user/repo.git",
8769
+ "remotes.fetchUrl": "提取",
8770
+ "remotes.pushUrl": "推送",
8771
+ "remotes.save": "保存",
8772
+ "remotes.saving": "保存中…",
8773
+ "remotes.edit": "修改地址",
8774
+ "remotes.remove": "移除",
8775
+ "remotes.confirmRemove": "确认移除",
8776
+ "remotes.cancel": "取消",
8777
+ "remotes.saved": "已更新远程 {name}",
8778
+ "remotes.removed": "已移除远程 {name}",
8779
+ "remotes.loadFailed": "读取远程地址失败",
8780
+ "remotes.nameEmpty": "请输入名称",
8781
+ "remotes.nameInvalid": "仅允许字母、数字、\".\"、\"_\"、\"-\"",
8782
+ "remotes.urlEmpty": "请输入地址",
8783
+ "remotes.urlInvalid": "请输入有效的远程地址"
7701
8784
  };
7702
8785
  //#endregion
7703
8786
  //#region src/client/index.ts