dsh-git-branch-manage 1.0.5 → 1.0.6

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
@@ -127,6 +127,8 @@ window.__ModuleLoader__.load({
127
127
  const ROUTE_LOG = `${ROUTE_PREFIX}/log`;
128
128
  /** POST ROUTE_PREFIX/commit-files — the files one commit changed. */
129
129
  const ROUTE_COMMIT_FILES = `${ROUTE_PREFIX}/commit-files`;
130
+ /** POST ROUTE_PREFIX/tag — create a tag at an existing commit. */
131
+ const ROUTE_TAG = `${ROUTE_PREFIX}/tag`;
130
132
  /** POST ROUTE_PREFIX/ai-commit-message — one-shot LLM commit message from
131
133
  * the staged diff (mounted only where the host provides the llm service). */
132
134
  const ROUTE_AI_COMMIT_MESSAGE = `${ROUTE_PREFIX}/ai-commit-message`;
@@ -388,16 +390,19 @@ window.__ModuleLoader__.load({
388
390
  /**
389
391
  * One page of commit history of the checkout.
390
392
  * @param repoPath - absolute directory inside the repository.
391
- * @param limit - page size (server-capped at 200).
392
- * @param skip - offset into the history.
393
- * @param ref - optional starting rev (a branch name); absent = HEAD.
393
+ * @param query - paging and the branch/author/time narrowing.
394
394
  */
395
- function requestLog(repoPath, limit, skip, ref) {
395
+ function requestLog(repoPath, query = {}) {
396
+ const { limit, skip, ref, all, author, since, until } = query;
396
397
  return post(ROUTE_LOG, {
397
398
  repoPath,
398
399
  ...limit === void 0 ? {} : { limit },
399
400
  ...skip === void 0 ? {} : { skip },
400
- ...ref === void 0 || ref === "" ? {} : { ref }
401
+ ...ref === void 0 || ref === "" ? {} : { ref },
402
+ ...all === true ? { all } : {},
403
+ ...author === void 0 || author === "" ? {} : { author },
404
+ ...since === void 0 ? {} : { since },
405
+ ...until === void 0 ? {} : { until }
401
406
  });
402
407
  }
403
408
  /**
@@ -412,6 +417,22 @@ window.__ModuleLoader__.load({
412
417
  });
413
418
  }
414
419
  /**
420
+ * Create a tag pointing at one commit (annotated when a message is given,
421
+ * lightweight otherwise). Local only: pushing tags is a separate release step.
422
+ * @param repoPath - absolute directory inside the repository.
423
+ * @param name - the new tag name.
424
+ * @param target - the commit to tag; absent = HEAD.
425
+ * @param message - annotation message; absent = lightweight tag.
426
+ */
427
+ function requestCreateTag(repoPath, name, target, message) {
428
+ return post(ROUTE_TAG, {
429
+ repoPath,
430
+ name,
431
+ ...target === void 0 || target === "" ? {} : { target },
432
+ ...message === void 0 || message.trim() === "" ? {} : { message }
433
+ });
434
+ }
435
+ /**
415
436
  * Generate a commit message from the checkout's diff with the requesting
416
437
  * session's own model (a one-shot host llm call).
417
438
  * @param body - the repo path plus the session's model selection and an
@@ -3650,11 +3671,97 @@ window.__ModuleLoader__.load({
3650
3671
  unstaged
3651
3672
  };
3652
3673
  }
3653
- /** Subject-case-insensitive filter over the commit subject and author. */
3654
- function filterCommits(commits, query) {
3655
- const needle = query.trim().toLowerCase();
3656
- if (needle === "") return [...commits];
3657
- return commits.filter((commit) => commit.subject.toLowerCase().includes(needle) || commit.authorName.toLowerCase().includes(needle));
3674
+ /** Day and week windows, in ms (7 days, not a calendar week). */
3675
+ const DAY_MS = 864e5;
3676
+ const WEEK_MS = 7 * DAY_MS;
3677
+ /**
3678
+ * Turn the picker's choice into the `since`/`until` bounds the history call
3679
+ * ships. `day`/`week` are relative to `now` — resolved per request, so a
3680
+ * panel left open an hour does not keep asking for the window it opened
3681
+ * with. `custom` carries the two bounds the date inputs produced (either may
3682
+ * be absent, meaning "open on that side").
3683
+ */
3684
+ function historyRangeBound(range, now, custom = {}) {
3685
+ if (range === "day") return { since: now - DAY_MS };
3686
+ if (range === "week") return { since: now - WEEK_MS };
3687
+ if (range === "custom") return {
3688
+ ...custom.since === void 0 ? {} : { since: custom.since },
3689
+ ...custom.until === void 0 ? {} : { until: custom.until }
3690
+ };
3691
+ return {};
3692
+ }
3693
+ /** No filter: every ref, every author, every date. */
3694
+ const EMPTY_HISTORY_FILTERS = {
3695
+ branch: "",
3696
+ author: "",
3697
+ range: "all",
3698
+ customSince: "",
3699
+ customUntil: ""
3700
+ };
3701
+ /** 'YYYY-MM-DD' as a date input emits it. */
3702
+ const DAY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
3703
+ /** Midnight of a 'YYYY-MM-DD' day in the BROWSER's zone, or undefined. Read
3704
+ * field by field on purpose: `new Date('2026-10-15')` is parsed as UTC
3705
+ * midnight, which is the previous day west of Greenwich — a custom window
3706
+ * would then start a day early for half the world. */
3707
+ function parseDayStart(value) {
3708
+ const match = DAY_PATTERN.exec(value.trim());
3709
+ if (match === null) return void 0;
3710
+ const [, year, month, day] = match;
3711
+ const date = new Date(Number(year), Number(month) - 1, Number(day));
3712
+ return Number.isFinite(date.getTime()) ? date.getTime() : void 0;
3713
+ }
3714
+ /** Last millisecond of a 'YYYY-MM-DD' day in the browser's zone, or undefined.
3715
+ * The end is inclusive: a custom window ending today must cover commits made
3716
+ * earlier today. */
3717
+ function parseDayEnd(value) {
3718
+ const start = parseDayStart(value);
3719
+ return start === void 0 ? void 0 : start + DAY_MS - 1;
3720
+ }
3721
+ /** The window the filters currently describe, relative to `now`. */
3722
+ function historyFilterBound(filters, now) {
3723
+ if (filters.range !== "custom") return historyRangeBound(filters.range, now);
3724
+ const since = parseDayStart(filters.customSince);
3725
+ const until = parseDayEnd(filters.customUntil);
3726
+ return {
3727
+ ...since === void 0 ? {} : { since },
3728
+ ...until === void 0 ? {} : { until }
3729
+ };
3730
+ }
3731
+ /**
3732
+ * The history request for one filter state. A chosen branch pins `ref` (and
3733
+ * cancels `all`, which would otherwise widen the walk to every ref); leaving
3734
+ * it blank asks for `all` — the card is meant to show the whole repository,
3735
+ * not just the checked-out branch, and merge edges only exist when the
3736
+ * branches on both sides of a merge are in the page.
3737
+ */
3738
+ function buildHistoryQuery(state) {
3739
+ const author = state.author.trim();
3740
+ const base = {
3741
+ ...author === "" ? {} : { author },
3742
+ ...state.bound
3743
+ };
3744
+ if (state.branch !== "") return {
3745
+ ...base,
3746
+ ref: state.branch
3747
+ };
3748
+ return {
3749
+ ...base,
3750
+ all: true
3751
+ };
3752
+ }
3753
+ /** The full paged request for one filter state, resolved against `now`. */
3754
+ function historyQueryFor(filters, now, limit, skip) {
3755
+ return {
3756
+ ...buildHistoryQuery({
3757
+ branch: filters.branch,
3758
+ author: filters.author,
3759
+ now,
3760
+ bound: historyFilterBound(filters, now)
3761
+ }),
3762
+ limit,
3763
+ skip
3764
+ };
3658
3765
  }
3659
3766
  /**
3660
3767
  * Case-insensitive substring filter over the branch name — the branch card's
@@ -3740,7 +3847,7 @@ window.__ModuleLoader__.load({
3740
3847
  }
3741
3848
  //#endregion
3742
3849
  //#region \0git-worktree-css:/home/runner/work/dsh-git-branch/dsh-git-branch/src/client/GitHistoryPanel.module.css?inline
3743
- const css$2 = "/* Git panel (sidebar global panel). Three stacked sections over the main\n * column: uncommitted changes + commit box, branches, commit history. Host\n * tokens only; the few status colors fall back to the same literals the\n * settings card uses. */\n\n.page_427795137 {\n display: flex;\n flex-direction: column;\n min-height: 0;\n height: 100%;\n}\n\n.topbar_427795137 {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 16px 8px;\n}\n\n.back_427795137 {\n flex: none;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n border: 0;\n background: none;\n cursor: pointer;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n padding: 4px 8px;\n border-radius: 8px;\n}\n\n.back_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n.title_427795137 {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n.repoPill_427795137 {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n font-size: 12px;\n line-height: 1.4;\n color: var(--dsw-alias-label-secondary);\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 999px;\n padding: 3px 10px;\n white-space: nowrap;\n overflow: hidden;\n}\n\n.repoName_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.branchName_427795137 {\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n.track_427795137 {\n font-variant-numeric: tabular-nums;\n}\n\n.topbarActions_427795137 {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 6px;\n flex: none;\n}\n\n.refreshBtn_427795137 {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: 0;\n border-radius: 8px;\n background: none;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n}\n\n.refreshBtn_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n.refreshBtn_427795137[disabled] {\n opacity: 0.5;\n cursor: default;\n}\n\n/* Cn-accelerate: the topbar 国内加速 button and its anchored popover. The\n * button's on-state is brand tint + brand text + green dot; the popover\n * mirrors the settings card's switch/input affordances over this panel's\n * tokens (popup chrome follows .menu_427795137 above). */\n\n.cnWrap_427795137 {\n position: relative;\n display: inline-flex;\n}\n\n.cnBtn_427795137,\n.cnBtnOn_427795137 {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n height: 28px;\n padding: 0 10px;\n border: 0;\n border-radius: 8px;\n background: none;\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n font-size: 12px;\n line-height: 1;\n cursor: pointer;\n}\n\n.cnBtn_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n.cnBtnOn_427795137 {\n background: rgba(76, 107, 245, 0.16);\n color: var(--dsw-alias-brand-primary);\n}\n\n.cnBtnOn_427795137:hover {\n background: rgba(76, 107, 245, 0.22);\n color: var(--dsw-alias-brand-primary);\n}\n\n.cnDot_427795137 {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--dsw-alias-status-success, #218358);\n}\n\n.cnPop_427795137 {\n position: absolute;\n right: 0;\n top: calc(100% + 6px);\n z-index: 40;\n width: 320px;\n background: var(--dsw-alias-surface-raised, var(--dsw-alias-bg-layer-3));\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n box-shadow: var(--dsw-shadow-lv3);\n backdrop-filter: var(--dsw-menu-backdrop-filter);\n padding: 12px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.cnPopHead_427795137 {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n\n.cnPopTitle_427795137 {\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n.cnSwitchRow_427795137 {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n}\n\n.cnSwitch_427795137 {\n width: 16px;\n height: 16px;\n accent-color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n}\n\n.cnSwitch_427795137:disabled {\n cursor: default;\n}\n\n.cnHint_427795137 {\n margin: 0;\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.cnFieldHead_427795137 {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n font-size: 12px;\n}\n\n.cnFieldLabel_427795137 {\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n.cnLink_427795137 {\n border: 0;\n background: none;\n padding: 0;\n font: inherit;\n font-size: 12px;\n color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n}\n\n.cnLink_427795137:hover {\n text-decoration: underline;\n}\n\n.cnLink_427795137:disabled {\n opacity: 0.5;\n cursor: default;\n text-decoration: none;\n}\n\n.cnInput_427795137,\n.cnInputBad_427795137 {\n box-sizing: border-box;\n width: 100%;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 12px;\n padding: 6px 8px;\n outline: none;\n}\n\n.cnInput_427795137:focus {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.cnInputBad_427795137,\n.cnInputBad_427795137:focus {\n border-color: var(--dsw-alias-status-danger, #d5304f);\n}\n\n.cnError_427795137 {\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-status-danger, #d5304f);\n}\n\n.cnFooter_427795137 {\n display: flex;\n justify-content: flex-end;\n gap: 6px;\n margin-top: 2px;\n}\n\n.cnGhost_427795137,\n.cnSave_427795137 {\n border: 0;\n border-radius: 6px;\n font: inherit;\n font-size: 12px;\n padding: 5px 12px;\n cursor: pointer;\n}\n\n.cnGhost_427795137 {\n background: none;\n color: var(--dsw-alias-label-secondary);\n}\n\n.cnGhost_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n.cnSave_427795137 {\n background: var(--dsw-alias-brand-primary);\n color: #fff;\n}\n\n.cnSave_427795137:disabled {\n opacity: 0.5;\n cursor: default;\n}\n\n.scroll_427795137 {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n padding: 0 16px 16px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n/* The page column below the topbar is a definite-height flex column and the\n * three cards split it 2:1:2 (uncommitted : branches : history) — five\n * shares, the topbar and gaps taking their natural height first. Each body\n * scrolls INSIDE its share — min-height: 0 is what lets a flex child shrink\n * below its content (the old natural-height model painted overflowing rows\n * over the sections below). A floor keeps a very short panel from\n * collapsing a card to its bare head; past the floor the column itself\n * scrolls. */\n.section_427795137 {\n border: 0.5px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-2);\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n\n.sectionFill_427795137 {\n flex: 1 1 0;\n min-height: 84px;\n}\n\n/* The three flex-grow overrides of the 2:1:2 split. */\n.sectionChanges_427795137 {\n flex-grow: 2;\n}\n\n.sectionBranches_427795137 {\n flex-grow: 1;\n}\n\n.sectionHistory_427795137 {\n flex-grow: 2;\n}\n\n.sectionHead_427795137 {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n.sectionTitle_427795137 {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary);\n}\n\n.countBadge_427795137 {\n font-size: 10px;\n line-height: 1;\n border-radius: 999px;\n padding: 3px 7px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-secondary);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n.sectionTools_427795137 {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.sectionBody_427795137 {\n padding: 10px 12px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n flex: 1 1 auto;\n min-height: 0;\n}\n\n/* The branch and history bodies scroll as a whole inside their share. */\n.sectionBodyScroll_427795137 {\n overflow-y: auto;\n overscroll-behavior: contain;\n}\n\n/* Inside the uncommitted card only the file lists scroll; the commit box\n * below stays pinned however long the change tree is. */\n.changeList_427795137 {\n display: flex;\n flex-direction: column;\n gap: 8px;\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n overscroll-behavior: contain;\n}\n\n/* The 全部展开/全部折叠 text chips in the branch card head. */\n.treeToggle_427795137 {\n background: none;\n border: 0;\n padding: 2px 5px;\n font: inherit;\n font-size: 11.5px;\n line-height: 1.4;\n color: var(--dsw-alias-label-secondary);\n cursor: pointer;\n border-radius: 5px;\n white-space: nowrap;\n}\n\n.treeToggle_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-brand-primary);\n}\n\n/* Small action chip used across the section heads and the commit row. */\n.act_427795137 {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n font: inherit;\n font-size: 12px;\n line-height: 1.4;\n cursor: pointer;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 7px;\n padding: 4px 10px;\n white-space: nowrap;\n}\n\n.act_427795137:hover:not([disabled]) {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.act_427795137[disabled] {\n opacity: 0.45;\n cursor: default;\n}\n\n.actPrimary_427795137 {\n /* Hardcoded blue by request — the brand token does not resolve reliably\n * inside the better-sidebar panel. */\n background: #4c6bf5;\n border-color: transparent;\n color: #fff;\n}\n\n.actPrimary_427795137:hover:not([disabled]) {\n border-color: transparent;\n filter: brightness(1.06);\n}\n\n/* Uncommitted changes */\n\n.groupTitleRow_427795137 {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.linkBtn_427795137 {\n margin-left: auto;\n background: none;\n border: 0;\n padding: 2px 4px;\n font: inherit;\n font-size: 12px;\n color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n border-radius: 5px;\n}\n\n.linkBtn_427795137:hover:not([disabled]) {\n text-decoration: underline;\n}\n\n.linkBtn_427795137[disabled] {\n opacity: 0.5;\n cursor: default;\n}\n\n.changeRow_427795137 {\n display: grid;\n grid-template-columns: 16px 18px minmax(0, 1fr) auto;\n gap: 8px;\n align-items: center;\n padding: 3px 6px;\n border-radius: 6px;\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n}\n\n.changeRow_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.changeCheck_427795137 {\n width: 14px;\n height: 14px;\n accent-color: var(--dsw-alias-brand-primary);\n cursor: pointer;\n margin: 0;\n}\n\n.statusBadge_427795137 {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n font-size: 10px;\n line-height: 1;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-weight: 700;\n color: #fff;\n box-sizing: border-box;\n}\n\n.statusA_427795137 { background: var(--dsw-alias-status-success, #218358); }\n.statusM_427795137 { background: var(--dsw-alias-label-warning, #b07000); }\n.statusD_427795137 { background: var(--dsw-alias-status-danger, #d5304f); }\n.statusR_427795137 { background: #8350d8; }\n.statusC_427795137 { background: #8350d8; }\n.statusT_427795137 { background: #5b7a9d; }\n.statusQ_427795137 { background: var(--dsw-alias-label-dimmed, #9aa0aa); }\n.statusU_427795137 { background: #d5502e; }\n\n.changePath_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: ui-monospace, Consolas, monospace;\n font-size: 11.5px;\n direction: rtl;\n text-align: left;\n}\n\n.stageHint_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n flex: none;\n}\n\n.commitBox_427795137 {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 4px;\n flex: none;\n}\n\n.commitInput_427795137 {\n width: 100%;\n box-sizing: border-box;\n resize: vertical;\n min-height: 52px;\n font: inherit;\n font-size: 12.5px;\n line-height: 1.5;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n padding: 7px 10px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n outline: none;\n}\n\n.commitInput_427795137:focus {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.commitActions_427795137 {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.commitHint_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n min-height: 16px;\n}\n\n.split_427795137 {\n margin-left: auto;\n display: flex;\n position: relative;\n}\n\n.splitMain_427795137 {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n padding: 6px 16px;\n font-size: 13px;\n}\n\n.splitCaret_427795137 {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n margin-left: 1px;\n padding: 6px 6px;\n}\n\n.menu_427795137 {\n position: absolute;\n right: 0;\n top: calc(100% + 4px);\n z-index: 40;\n width: max-content;\n min-width: 0;\n background: var(--dsw-alias-surface-raised, var(--dsw-alias-bg-layer-3));\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n box-shadow: var(--dsw-shadow-lv3);\n backdrop-filter: var(--dsw-menu-backdrop-filter);\n padding: 3px;\n display: flex;\n flex-direction: column;\n}\n\n.menuItem_427795137 {\n display: block;\n width: 100%;\n text-align: left;\n background: none;\n border: 0;\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 12px;\n line-height: 1.4;\n padding: 5px 10px;\n border-radius: 5px;\n cursor: pointer;\n white-space: nowrap;\n}\n\n.menuItem_427795137:hover:not([disabled]) {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.menuItem_427795137[disabled] {\n opacity: 0.5;\n cursor: default;\n}\n\n.menuSub_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n margin-top: 1px;\n}\n\n/* Branch section — two IDEA-style trees: 本地 → branches, 远程 → per-remote → branches */\n\n.treeHead_427795137 {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 6px;\n border-radius: 6px;\n cursor: pointer;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-label-secondary);\n user-select: none;\n}\n\n.treeHead_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-primary);\n}\n\n.treeChevron_427795137 {\n display: inline-flex;\n align-items: center;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.treeChildren_427795137 {\n display: flex;\n flex-direction: column;\n margin-left: 12px;\n}\n\n.treeHeadNest_427795137 {\n font-weight: 400;\n}\n\n.treeNest_427795137 {\n margin-left: 12px;\n}\n\n.branchRow_427795137 {\n display: grid;\n grid-template-columns: 20px minmax(0, 1fr) auto;\n gap: 8px;\n align-items: center;\n padding: 5px 8px;\n border-radius: 7px;\n /* The action menu opens on RIGHT-CLICK (Enter/Space for keyboard) — an\n * arrow, not the pointing hand a left-click affordance would promise. */\n cursor: default;\n font-size: 12.5px;\n color: var(--dsw-alias-label-primary);\n}\n\n.branchRow_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n/* Whole-row highlight for the current branch: the ✓ alone read as too\n * quiet inside the tree. A translucent brand-blue wash plus the inset left\n * accent and a colored name survives both themes; #4c6bf5 is the same\n * hardcoded blue actPrimary uses (the brand token does not resolve\n * reliably inside the better-sidebar panel). */\n.branchRowCurrent_427795137 {\n background: rgba(76, 107, 245, 0.16);\n box-shadow: inset 2px 0 0 #4c6bf5;\n cursor: default;\n}\n\n.branchRowCurrent_427795137:hover {\n background: rgba(76, 107, 245, 0.16);\n}\n\n.branchRowCurrent_427795137 .branchName_427795137 {\n color: #4c6bf5;\n font-weight: 600;\n}\n\n.branchMark_427795137 {\n color: var(--dsw-alias-brand-primary);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n}\n\n.branchName_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 500;\n}\n\n.branchKind_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 10px;\n margin-left: 6px;\n}\n\n.branchTrack_427795137 {\n color: var(--dsw-alias-label-tertiary);\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n flex: none;\n}\n\n/* The branch action menu is portaled; fixed positioning by row rect. */\n.floatMenu_427795137 {\n position: fixed;\n z-index: 60;\n min-width: 190px;\n}\n\n.menuHead_427795137 {\n padding: 6px 10px 5px;\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary);\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n margin-bottom: 3px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.menuHeadStrong_427795137 {\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n.menuDanger_427795137 {\n color: var(--dsw-alias-status-danger, #d5304f);\n}\n\n.formField_427795137 {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.formLabel_427795137 {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.formInput_427795137 {\n font: inherit;\n font-size: 13px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n padding: 7px 10px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n outline: none;\n}\n\n.formInput_427795137:focus {\n border-color: var(--dsw-alias-brand-primary);\n}\n\n.formError_427795137 {\n font-size: 12px;\n color: var(--dsw-alias-status-danger, #d5304f);\n min-height: 16px;\n}\n\n/* Commit history */\n\n.historyHead_427795137 {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-bottom: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n.searchBox_427795137 {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 6px;\n flex: 1;\n max-width: 300px;\n min-width: 140px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 7px;\n padding: 0 9px;\n height: 28px;\n color: var(--dsw-alias-label-tertiary);\n background: var(--dsw-alias-bg-layer-3);\n}\n\n/* The branch card's head variant: sits right of the title and grows toward\n * the tools instead of being right-aligned by `margin-left: auto`. The\n * overrides win because both selectors are single classes and this one is\n * declared later. */\n.searchBoxHead_427795137 {\n margin-left: 0;\n flex: 1 1 auto;\n max-width: 320px;\n min-width: 90px;\n}\n\n.searchInput_427795137 {\n flex: 1;\n min-width: 0;\n border: 0;\n background: none;\n outline: none;\n font: inherit;\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n}\n\n.searchInput_427795137::placeholder {\n color: var(--dsw-alias-label-tertiary);\n}\n\n.commitRow_427795137 {\n position: relative;\n display: grid;\n grid-template-columns: 22px minmax(0, 1fr) auto;\n gap: 8px;\n align-items: center;\n padding: 6px 8px 6px 2px;\n cursor: pointer;\n border-radius: 7px;\n}\n\n.commitRow_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.commitRowOpen_427795137 {\n background: var(--dsw-alias-bg-layer-3);\n}\n\n.commitRowOpen_427795137:hover {\n background: var(--dsw-alias-bg-layer-3);\n}\n\n.commitGraph_427795137 {\n position: relative;\n align-self: stretch;\n min-height: 34px;\n}\n\n.commitGraph_427795137::before {\n content: \"\";\n position: absolute;\n left: 10px;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--dsw-alias-border-l2);\n}\n\n.commitRowFirst_427795137 .commitGraph_427795137::before {\n top: 50%;\n}\n\n.commitRowLast_427795137 .commitGraph_427795137::before {\n bottom: 50%;\n}\n\n/* A small plain dot on the rail — the old 10px dot with a fat card-colored\n * ring read as a heavy black blob. #4c6bf5 for the same reason actPrimary\n * hardcodes it (the brand token does not resolve reliably in this panel). */\n.commitDot_427795137 {\n position: absolute;\n left: 8px;\n top: 50%;\n transform: translateY(-50%);\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: #4c6bf5;\n}\n\n.commitDotMerge_427795137 {\n background: #8350d8;\n}\n\n.commitMain_427795137 {\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 1px;\n}\n\n.commitSubject_427795137 {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n font-size: 12.5px;\n color: var(--dsw-alias-label-primary);\n}\n\n.commitSubjectText_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.commitRowOpen_427795137 .commitSubjectText_427795137 {\n font-weight: 600;\n}\n\n.refBadges_427795137 {\n display: inline-flex;\n gap: 4px;\n flex: none;\n}\n\n.refBadge_427795137 {\n font-size: 10px;\n line-height: 1;\n border-radius: 4px;\n padding: 3px 5px;\n font-weight: 500;\n white-space: nowrap;\n max-width: 140px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.refHead_427795137 {\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary-bluish);\n border: 0.5px solid var(--dsw-alias-border-l4);\n}\n\n.refTag_427795137 {\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-warning, #b07000);\n border: 0.5px solid var(--dsw-alias-border-l4);\n}\n\n.refRemote_427795137 {\n background: var(--dsw-alias-bg-layer-3);\n color: #8350d8;\n border: 0.5px solid var(--dsw-alias-border-l4);\n}\n\n.refLocal_427795137 {\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-status-success, #218358);\n border: 0.5px solid var(--dsw-alias-border-l4);\n}\n\n.commitMeta_427795137 {\n display: flex;\n align-items: center;\n gap: 6px;\n color: var(--dsw-alias-label-tertiary);\n font-size: 11.5px;\n}\n\n.commitSide_427795137 {\n display: flex;\n align-items: center;\n gap: 8px;\n color: var(--dsw-alias-label-tertiary);\n font-size: 11.5px;\n flex: none;\n}\n\n.commitHash_427795137 {\n font-family: ui-monospace, Consolas, monospace;\n}\n\n.commitExpand_427795137 {\n grid-column: 1 / -1;\n padding: 2px 4px 8px 28px;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.commitExpandTitle_427795137 {\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary);\n margin-bottom: 3px;\n}\n\n.fileRow_427795137 {\n display: grid;\n grid-template-columns: 18px minmax(0, 1fr) auto;\n gap: 8px;\n align-items: center;\n padding: 3px 6px;\n border-radius: 6px;\n font-size: 12px;\n color: var(--dsw-alias-label-primary);\n}\n\n.fileRow_427795137:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.filePath_427795137 {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: ui-monospace, Consolas, monospace;\n font-size: 11.5px;\n direction: rtl;\n text-align: left;\n}\n\n.fileNums_427795137 {\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-tertiary);\n flex: none;\n}\n\n.fileAdd_427795137 { color: var(--dsw-alias-status-success, #218358); }\n.fileDel_427795137 { color: var(--dsw-alias-status-danger, #d5304f); }\n\n.empty_427795137 {\n padding: 28px 16px;\n text-align: center;\n color: var(--dsw-alias-label-tertiary);\n font-size: 12.5px;\n}\n\n.errorText_427795137 {\n padding: 20px 16px;\n text-align: center;\n color: var(--dsw-alias-status-danger, #d5304f);\n font-size: 12.5px;\n}\n\n.moreRow_427795137 {\n display: flex;\n justify-content: center;\n padding: 8px 0 2px;\n}\n";
3850
+ 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";
3744
3851
  const tagId$2 = "dsh-git-branch-manage/GitHistoryPanel.module.css";
3745
3852
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=\"" + tagId$2 + "\"]") === null) {
3746
3853
  const tag = document.createElement("style");
@@ -3842,13 +3949,18 @@ window.__ModuleLoader__.load({
3842
3949
  "searchBox": "searchBox_427795137",
3843
3950
  "searchBoxHead": "searchBoxHead_427795137",
3844
3951
  "searchInput": "searchInput_427795137",
3952
+ "historyFilters": "historyFilters_427795137",
3953
+ "filterSelect": "filterSelect_427795137",
3954
+ "filterInput": "filterInput_427795137",
3955
+ "filterDate": "filterDate_427795137",
3956
+ "filterDash": "filterDash_427795137",
3957
+ "commitList": "commitList_427795137",
3845
3958
  "commitRow": "commitRow_427795137",
3846
3959
  "commitRowOpen": "commitRowOpen_427795137",
3847
3960
  "commitGraph": "commitGraph_427795137",
3848
- "commitRowFirst": "commitRowFirst_427795137",
3849
- "commitRowLast": "commitRowLast_427795137",
3961
+ "commitGraphSvg": "commitGraphSvg_427795137",
3850
3962
  "commitDot": "commitDot_427795137",
3851
- "commitDotMerge": "commitDotMerge_427795137",
3963
+ "commitExpandGraph": "commitExpandGraph_427795137",
3852
3964
  "commitMain": "commitMain_427795137",
3853
3965
  "commitSubject": "commitSubject_427795137",
3854
3966
  "commitSubjectText": "commitSubjectText_427795137",
@@ -3859,7 +3971,9 @@ window.__ModuleLoader__.load({
3859
3971
  "refRemote": "refRemote_427795137",
3860
3972
  "refLocal": "refLocal_427795137",
3861
3973
  "commitMeta": "commitMeta_427795137",
3974
+ "commitAuthor": "commitAuthor_427795137",
3862
3975
  "commitSide": "commitSide_427795137",
3976
+ "commitDate": "commitDate_427795137",
3863
3977
  "commitHash": "commitHash_427795137",
3864
3978
  "commitExpand": "commitExpand_427795137",
3865
3979
  "commitExpandTitle": "commitExpandTitle_427795137",
@@ -3873,6 +3987,67 @@ window.__ModuleLoader__.load({
3873
3987
  "moreRow": "moreRow_427795137"
3874
3988
  };
3875
3989
  //#endregion
3990
+ //#region src/client/panel/FloatMenu.tsx
3991
+ /**
3992
+ * FloatMenu: a body-portalled, fixed-positioned popup menu anchored to a
3993
+ * viewport point — the shared shell behind the branch list's context menu and
3994
+ * the history rows' commit menu.
3995
+ *
3996
+ * Owns the three things every such menu needs and neither caller should
3997
+ * re-implement: the portal (a scrolling card body would otherwise clip it),
3998
+ * dismissal (pointer-down outside + Escape), and viewport clamping in a
3999
+ * LAYOUT effect (measuring after paint would flash a menu off-screen, and the
4000
+ * clamp must run before the browser shows the frame).
4001
+ *
4002
+ * Position arrives as the anchor point in client coordinates — the caller
4003
+ * derives it from the row it was opened on, so the menu opens where the user
4004
+ * clicked rather than where the row happens to be after a re-render.
4005
+ *
4006
+ * @module git-worktree/client/panel/FloatMenu
4007
+ */
4008
+ /** Distance kept from the viewport edges, in px. */
4009
+ const VIEWPORT_MARGIN = 8;
4010
+ /**
4011
+ * One floating menu. Rendered into `document.body`, positioned on the layout
4012
+ * pass and clamped into the viewport: flip above the anchor when the menu
4013
+ * would overflow the bottom, clamp the left edge otherwise.
4014
+ */
4015
+ function FloatMenu(props) {
4016
+ const { anchor, role = "menu", label, onClose, children } = props;
4017
+ const ref = (0, react.useRef)(null);
4018
+ (0, react.useEffect)(() => {
4019
+ const onPointerDown = (event) => {
4020
+ if (ref.current !== null && !ref.current.contains(event.target)) onClose();
4021
+ };
4022
+ const onKeyDown = (event) => {
4023
+ if (event.key === "Escape") onClose();
4024
+ };
4025
+ document.addEventListener("pointerdown", onPointerDown, true);
4026
+ document.addEventListener("keydown", onKeyDown);
4027
+ return () => {
4028
+ document.removeEventListener("pointerdown", onPointerDown, true);
4029
+ document.removeEventListener("keydown", onKeyDown);
4030
+ };
4031
+ }, [onClose]);
4032
+ (0, react.useLayoutEffect)(() => {
4033
+ const el = ref.current;
4034
+ if (el === null) return;
4035
+ const rect = el.getBoundingClientRect();
4036
+ const left = Math.max(VIEWPORT_MARGIN, Math.min(anchor.left, window.innerWidth - rect.width - VIEWPORT_MARGIN));
4037
+ let top = anchor.top;
4038
+ if (top + rect.height > window.innerHeight - VIEWPORT_MARGIN) top = Math.max(VIEWPORT_MARGIN, anchor.top - rect.height - VIEWPORT_MARGIN);
4039
+ el.style.left = `${String(Math.round(left))}px`;
4040
+ el.style.top = `${String(Math.round(top))}px`;
4041
+ }, [anchor]);
4042
+ return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4043
+ ref,
4044
+ className: `${GitHistoryPanel_module_default.menu} ${GitHistoryPanel_module_default.floatMenu}`,
4045
+ role,
4046
+ ...label === void 0 ? {} : { "aria-label": label },
4047
+ children
4048
+ }), document.body);
4049
+ }
4050
+ //#endregion
3876
4051
  //#region src/client/panel/BranchSection.tsx
3877
4052
  /**
3878
4053
  * BranchSection: the panel's middle block — the branch list with an IDEA-style
@@ -3910,35 +4085,9 @@ window.__ModuleLoader__.load({
3910
4085
  function BranchFloatMenu(props) {
3911
4086
  const { state, t, busy, onAction, onClose } = props;
3912
4087
  const { branch } = state;
3913
- const ref = (0, react.useRef)(null);
3914
- (0, react.useEffect)(() => {
3915
- const onPointerDown = (event) => {
3916
- if (ref.current !== null && !ref.current.contains(event.target)) onClose();
3917
- };
3918
- const onKeyDown = (event) => {
3919
- if (event.key === "Escape") onClose();
3920
- };
3921
- document.addEventListener("pointerdown", onPointerDown, true);
3922
- document.addEventListener("keydown", onKeyDown);
3923
- return () => {
3924
- document.removeEventListener("pointerdown", onPointerDown, true);
3925
- document.removeEventListener("keydown", onKeyDown);
3926
- };
3927
- }, [onClose]);
3928
- (0, react.useLayoutEffect)(() => {
3929
- const el = ref.current;
3930
- if (el === null) return;
3931
- const rect = el.getBoundingClientRect();
3932
- const left = Math.max(8, Math.min(state.rect.left, window.innerWidth - rect.width - 8));
3933
- let top = state.rect.top;
3934
- if (top + rect.height > window.innerHeight - 8) top = Math.max(8, state.rect.top - rect.height - 8);
3935
- el.style.left = `${String(Math.round(left))}px`;
3936
- el.style.top = `${String(Math.round(top))}px`;
3937
- }, [state]);
3938
- return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3939
- ref,
3940
- className: `${GitHistoryPanel_module_default.menu} ${GitHistoryPanel_module_default.floatMenu}`,
3941
- role: "menu",
4088
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(FloatMenu, {
4089
+ anchor: state.rect,
4090
+ onClose,
3942
4091
  children: [
3943
4092
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3944
4093
  className: GitHistoryPanel_module_default.menuHead,
@@ -3993,7 +4142,7 @@ window.__ModuleLoader__.load({
3993
4142
  children: t("panel.branchCopy")
3994
4143
  })
3995
4144
  ]
3996
- }), document.body);
4145
+ });
3997
4146
  }
3998
4147
  /** The branches section. */
3999
4148
  function BranchSection(props) {
@@ -4439,18 +4588,212 @@ window.__ModuleLoader__.load({
4439
4588
  });
4440
4589
  }
4441
4590
  //#endregion
4591
+ //#region src/client/git-graph.ts
4592
+ /** Lazily extend `lanes` so `index` exists. */
4593
+ function ensureLane(lanes, index) {
4594
+ while (lanes.length <= index) lanes.push(null);
4595
+ }
4596
+ /** First free lane, appended when every existing lane is busy. */
4597
+ function takeLane(lanes) {
4598
+ const free = lanes.indexOf(null);
4599
+ if (free >= 0) return free;
4600
+ lanes.push(null);
4601
+ return lanes.length - 1;
4602
+ }
4603
+ /**
4604
+ * Compute one row per commit, in the order given.
4605
+ * @param commits - newest-first commits (git log order).
4606
+ * @returns one {@link GraphRow} per input commit, same order.
4607
+ */
4608
+ function buildGraph(commits) {
4609
+ /** Lane state at the TOP edge of the current row. */
4610
+ const lanes = [];
4611
+ const rows = [];
4612
+ for (const commit of commits) {
4613
+ const incoming = [];
4614
+ for (let index = 0; index < lanes.length; index += 1) if (lanes[index] === commit.hash) incoming.push(index);
4615
+ const converge = incoming[0];
4616
+ const lane = converge === void 0 ? takeLane(lanes) : converge;
4617
+ ensureLane(lanes, lane);
4618
+ if (converge === void 0) incoming.push(lane);
4619
+ const top = lanes.slice();
4620
+ for (const index of incoming) lanes[index] = null;
4621
+ const parentLanes = [];
4622
+ for (let position = 0; position < commit.parents.length; position += 1) {
4623
+ const parent = commit.parents[position];
4624
+ let target;
4625
+ if (position === 0) {
4626
+ target = lane;
4627
+ ensureLane(lanes, target);
4628
+ } else {
4629
+ const waiting = lanes.indexOf(parent);
4630
+ target = waiting >= 0 ? waiting : takeLane(lanes);
4631
+ }
4632
+ lanes[target] = parent;
4633
+ if (!parentLanes.includes(target)) parentLanes.push(target);
4634
+ }
4635
+ const edges = [];
4636
+ for (let index = 0; index < Math.max(top.length, lanes.length); index += 1) {
4637
+ const above = top[index] ?? null;
4638
+ const below = lanes[index] ?? null;
4639
+ if (above === null) continue;
4640
+ if (below === above && index !== lane) {
4641
+ edges.push({
4642
+ kind: "through",
4643
+ from: index,
4644
+ to: index
4645
+ });
4646
+ continue;
4647
+ }
4648
+ edges.push({
4649
+ kind: "in",
4650
+ from: index,
4651
+ to: lane
4652
+ });
4653
+ }
4654
+ for (const target of parentLanes) edges.push({
4655
+ kind: "out",
4656
+ from: lane,
4657
+ to: target
4658
+ });
4659
+ rows.push({
4660
+ lane,
4661
+ lanes: Math.max(top.length, lanes.length),
4662
+ edges
4663
+ });
4664
+ }
4665
+ return trimWidths(rows);
4666
+ }
4667
+ /**
4668
+ * Narrow each row to the lanes it actually uses: a lane released midway (or
4669
+ * one opened only by a later commit) would otherwise reserve dead space in
4670
+ * every row above it.
4671
+ */
4672
+ function trimWidths(rows) {
4673
+ const widths = rows.map((row) => {
4674
+ let width = 0;
4675
+ for (const edge of row.edges) width = Math.max(width, edge.from + 1, edge.to + 1);
4676
+ return Math.max(width, row.lane + 1);
4677
+ });
4678
+ return rows.map((row, index) => ({
4679
+ ...row,
4680
+ lanes: widths[index] ?? 1
4681
+ }));
4682
+ }
4683
+ /** The widest row of a graph — how many lane columns the gutter needs. */
4684
+ function graphWidth(rows) {
4685
+ return rows.reduce((widest, row) => Math.max(widest, row.lanes), 1);
4686
+ }
4687
+ /** Vertical coordinate system for curves, scaled to the actual row height. */
4688
+ const GRAPH_BAND = 44;
4689
+ const LINE_WIDTH = 1.6;
4690
+ const LANE_COLORS = [
4691
+ "#4c6bf5",
4692
+ "#2f9e44",
4693
+ "#e8590c",
4694
+ "#9c36b5",
4695
+ "#0c8599",
4696
+ "#d6336c",
4697
+ "#f08c00",
4698
+ "#5f3dc4"
4699
+ ];
4700
+ function laneColor(lane) {
4701
+ return LANE_COLORS[(lane % LANE_COLORS.length + LANE_COLORS.length) % LANE_COLORS.length];
4702
+ }
4703
+ function cx(lane) {
4704
+ return lane * 14 + 7 + 2;
4705
+ }
4706
+ function bend(from, fromY, to, toY) {
4707
+ const x0 = cx(from);
4708
+ const x1 = cx(to);
4709
+ if (x0 === x1) return `M ${String(x0)} ${String(fromY)} L ${String(x1)} ${String(toY)}`;
4710
+ const mid = (toY - fromY) / 2;
4711
+ return `M ${String(x0)} ${String(fromY)} C ${String(x0)} ${String(fromY + mid)} ${String(x1)} ${String(toY - mid)} ${String(x1)} ${String(toY)}`;
4712
+ }
4713
+ function CommitGraph({ row, width, merge }) {
4714
+ const mid = GRAPH_BAND / 2;
4715
+ const dotColor = laneColor(row.lane);
4716
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
4717
+ className: GitHistoryPanel_module_default.commitGraphSvg,
4718
+ width,
4719
+ height: "100%",
4720
+ viewBox: `0 0 ${String(width)} ${String(GRAPH_BAND)}`,
4721
+ preserveAspectRatio: "none",
4722
+ "aria-hidden": "true",
4723
+ focusable: "false",
4724
+ children: row.edges.map((edge, index) => {
4725
+ const [fromY, toY] = edge.kind === "through" ? [0, GRAPH_BAND] : edge.kind === "in" ? [0, mid] : [mid, GRAPH_BAND];
4726
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
4727
+ d: bend(edge.from, fromY, edge.to, toY),
4728
+ fill: "none",
4729
+ stroke: laneColor(edge.kind === "out" ? edge.to : edge.from),
4730
+ strokeWidth: LINE_WIDTH,
4731
+ strokeLinecap: "round",
4732
+ vectorEffect: "non-scaling-stroke"
4733
+ }, `${edge.kind}:${String(edge.from)}:${String(edge.to)}:${String(index)}`);
4734
+ })
4735
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4736
+ className: GitHistoryPanel_module_default.commitDot,
4737
+ style: {
4738
+ left: cx(row.lane),
4739
+ color: dotColor,
4740
+ backgroundColor: merge ? "transparent" : dotColor
4741
+ },
4742
+ "aria-hidden": "true"
4743
+ })] });
4744
+ }
4745
+ /** Only bottom-edge lanes continue through an expanded file list. Incoming
4746
+ * lanes already terminate at the dot and must not be resurrected below it. */
4747
+ function CommitGraphContinuation({ row, width }) {
4748
+ const lanes = [...new Set(row.edges.filter((edge) => edge.kind !== "in").map((edge) => edge.to))];
4749
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
4750
+ className: GitHistoryPanel_module_default.commitExpandGraph,
4751
+ width,
4752
+ height: "100%",
4753
+ "aria-hidden": "true",
4754
+ focusable: "false",
4755
+ children: lanes.map((lane) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
4756
+ x1: cx(lane),
4757
+ x2: cx(lane),
4758
+ y1: "0",
4759
+ y2: "100%",
4760
+ stroke: laneColor(lane),
4761
+ strokeWidth: LINE_WIDTH,
4762
+ strokeLinecap: "round"
4763
+ }, lane))
4764
+ });
4765
+ }
4766
+ //#endregion
4442
4767
  //#region src/client/panel/HistorySection.tsx
4443
4768
  /**
4444
- * HistorySection: the panel's third block — the commit list. One row shows
4445
- * the subject, its refs badges, the author and the relative time, and the
4446
- * file count with the short hash; hovering a row raises the framework
4447
- * Tooltip (subject · author <email> · absolute time · hash); CLICKING a row
4448
- * expands the commit's changed files INLINE below it (exactly one open at a
4449
- * time), loading them on first open. The search input filters the loaded
4450
- * page client-side; "load more" fetches the next page.
4769
+ * HistorySection: the panel's third block — the commit graph. Every row
4770
+ * draws its lane picture (vertical rails, curved merges, hollow dots for
4771
+ * merge commits) from `buildGraph`, then the subject, its refs badges, the
4772
+ * author, and the absolute commit time. CLICKING a row expands the commit's
4773
+ * changed files INLINE below it (exactly one open at a time), loading them on
4774
+ * first open; RIGHT-CLICKING it opens the commit menu (copy hash, copy
4775
+ * subject, new branch here, tag here), which is where the history card's two
4776
+ * writing verbs live.
4777
+ *
4778
+ * The head carries the three filters the card asks for, in this order —
4779
+ * author, branch (a ref to pin the walk on, or every ref), and a time window
4780
+ * (all / last 24h / last 7 days / a custom day range). They are NOT applied
4781
+ * here: this section is presentational, so it hands each change up and the
4782
+ * data owner refetches.
4783
+ * Filtering client-side would have been wrong for a paged list — the page
4784
+ * holds 50 commits, and a needle outside it would silently report "no match".
4451
4785
  *
4452
4786
  * @module git-worktree/client/panel/HistorySection
4453
4787
  */
4788
+ /** `YYYY/MM/DD HH:mm` in the browser's zone — the reference screenshot's
4789
+ * right-hand time column. Field-by-field on purpose: `toLocaleString` varies
4790
+ * with the user's locale and would break the column's alignment. */
4791
+ function formatStamp(date) {
4792
+ if (!Number.isFinite(date) || date <= 0) return "";
4793
+ const at = new Date(date);
4794
+ const pad = (value) => String(value).padStart(2, "0");
4795
+ return `${String(at.getFullYear())}/${pad(at.getMonth() + 1)}/${pad(at.getDate())} ${pad(at.getHours())}:${pad(at.getMinutes())}`;
4796
+ }
4454
4797
  function statusTitle$1(status, t) {
4455
4798
  if (status === "A") return t("panel.stAdd");
4456
4799
  if (status === "D") return t("panel.stDel");
@@ -4494,18 +4837,12 @@ window.__ModuleLoader__.load({
4494
4837
  /** One history row; `ref` and the tooltip's hover handlers arrive through
4495
4838
  * the anchor share when the row is wrapped in a Tooltip. */
4496
4839
  const CommitRow = (0, react.forwardRef)(function CommitRow(props, ref) {
4497
- const { commit, first, last, open, files, t, onToggle, timeLabelFor } = props;
4840
+ const { commit, graph, graphWidth, open, files, t, onToggle, onMenu } = props;
4498
4841
  const badges = classifyRefs(commit.refs);
4499
- const when = new Date(commit.date);
4500
- const absolute = Number.isFinite(commit.date) && commit.date > 0 ? `${when.toLocaleDateString()} ${when.toLocaleTimeString()}` : "";
4842
+ const stamp = formatStamp(commit.date);
4501
4843
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4502
4844
  ref,
4503
- className: [
4504
- GitHistoryPanel_module_default.commitRow,
4505
- first ? GitHistoryPanel_module_default.commitRowFirst : "",
4506
- last ? GitHistoryPanel_module_default.commitRowLast : "",
4507
- open ? GitHistoryPanel_module_default.commitRowOpen : ""
4508
- ].filter((name) => name !== "").join(" "),
4845
+ className: [GitHistoryPanel_module_default.commitRow, open ? GitHistoryPanel_module_default.commitRowOpen : ""].filter((name) => name !== "").join(" "),
4509
4846
  role: "button",
4510
4847
  tabIndex: 0,
4511
4848
  "aria-expanded": open,
@@ -4515,6 +4852,13 @@ window.__ModuleLoader__.load({
4515
4852
  onFocus: props.onFocus,
4516
4853
  onBlur: props.onBlur,
4517
4854
  onClick: () => onToggle(commit.hash),
4855
+ onContextMenu: (event) => {
4856
+ event.preventDefault();
4857
+ onMenu(commit, {
4858
+ left: event.clientX,
4859
+ top: event.clientY
4860
+ });
4861
+ },
4518
4862
  onKeyDown: (event) => {
4519
4863
  if (event.key === "Enter" || event.key === " ") {
4520
4864
  event.preventDefault();
@@ -4524,7 +4868,11 @@ window.__ModuleLoader__.load({
4524
4868
  children: [
4525
4869
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4526
4870
  className: GitHistoryPanel_module_default.commitGraph,
4527
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: `${GitHistoryPanel_module_default.commitDot} ${commit.parents.length > 1 ? GitHistoryPanel_module_default.commitDotMerge : ""}` })
4871
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommitGraph, {
4872
+ row: graph,
4873
+ width: graphWidth,
4874
+ merge: commit.parents.length > 1
4875
+ })
4528
4876
  }),
4529
4877
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4530
4878
  className: GitHistoryPanel_module_default.commitMain,
@@ -4540,37 +4888,48 @@ window.__ModuleLoader__.load({
4540
4888
  children: badge.text
4541
4889
  }, `${badge.kind}:${badge.text}`))
4542
4890
  })]
4543
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4891
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4544
4892
  className: GitHistoryPanel_module_default.commitMeta,
4545
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: commit.authorName }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: timeLabelFor(commit.date) })]
4893
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4894
+ className: GitHistoryPanel_module_default.commitAuthor,
4895
+ children: commit.authorName
4896
+ })
4546
4897
  })]
4547
4898
  }),
4548
4899
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4549
4900
  className: GitHistoryPanel_module_default.commitSide,
4550
4901
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4551
- className: GitHistoryPanel_module_default.commitHash,
4552
- children: commit.short
4902
+ className: GitHistoryPanel_module_default.commitDate,
4903
+ title: stamp,
4904
+ children: stamp
4553
4905
  })
4554
4906
  }),
4555
4907
  open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4556
4908
  className: GitHistoryPanel_module_default.commitExpand,
4557
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4558
- className: GitHistoryPanel_module_default.commitExpandTitle,
4559
- children: t("panel.filesTitle", {
4560
- n: files?.kind === "loaded" ? files.files.length : 0,
4561
- author: commit.authorName,
4562
- date: absolute
4563
- })
4564
- }), files === void 0 || files.kind === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4565
- className: GitHistoryPanel_module_default.commitExpandTitle,
4566
- children: t("manager.loading")
4567
- }) : files.kind === "failed" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4568
- className: GitHistoryPanel_module_default.errorText,
4569
- children: files.message
4570
- }) : files.files.map((file) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FileRow, {
4571
- file,
4572
- t
4573
- }, `${file.status}:${file.path}`))]
4909
+ children: [
4910
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommitGraphContinuation, {
4911
+ row: graph,
4912
+ width: graphWidth
4913
+ }),
4914
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4915
+ className: GitHistoryPanel_module_default.commitExpandTitle,
4916
+ children: t("panel.filesTitle", {
4917
+ n: files?.kind === "loaded" ? files.files.length : 0,
4918
+ author: commit.authorName,
4919
+ date: stamp
4920
+ })
4921
+ }),
4922
+ files === void 0 || files.kind === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4923
+ className: GitHistoryPanel_module_default.commitExpandTitle,
4924
+ children: t("manager.loading")
4925
+ }) : files.kind === "failed" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4926
+ className: GitHistoryPanel_module_default.errorText,
4927
+ children: files.message
4928
+ }) : files.files.map((file) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FileRow, {
4929
+ file,
4930
+ t
4931
+ }, `${file.status}:${file.path}`))
4932
+ ]
4574
4933
  }) : null
4575
4934
  ]
4576
4935
  });
@@ -4582,70 +4941,303 @@ window.__ModuleLoader__.load({
4582
4941
  return `${commit.subject} · ${commit.authorName} <${commit.authorEmail}> · ${absolute} · ${commit.short}`;
4583
4942
  }
4584
4943
  function HistorySection(props) {
4585
- const { t, commits, loaded, loadError, loading, hasMore, loadingMore, expanded, filesByHash, onLoadMore, onToggle, timeLabelFor } = props;
4586
- const [query, setQuery] = (0, react.useState)("");
4587
- const needle = query.trim().toLowerCase();
4588
- const visible = filterCommits(commits, query);
4944
+ const { t, commits, loaded, loadError, loading, hasMore, loadingMore, expanded, filesByHash, filters, branches, busy, onLoadMore, onToggle, onFilters, onCreateBranch, onCreateTag, pushToast } = props;
4945
+ const [menu, setMenu] = (0, react.useState)(null);
4946
+ const [dialog, setDialog] = (0, react.useState)(null);
4947
+ const [draft, setDraft] = (0, react.useState)("");
4948
+ const [message, setMessage] = (0, react.useState)("");
4949
+ const [authorDraft, setAuthorDraft] = (0, react.useState)(filters.author);
4950
+ const graph = (0, react.useMemo)(() => buildGraph(commits), [commits]);
4951
+ const bandWidth = (0, react.useMemo)(() => graphWidth(graph) * 14 + 4, [graph]);
4952
+ const openMenu = (commit, rect) => {
4953
+ setMenu({
4954
+ commit,
4955
+ rect
4956
+ });
4957
+ };
4958
+ const openDialog = (kind, commit) => {
4959
+ setMenu(null);
4960
+ setDraft("");
4961
+ setMessage("");
4962
+ setDialog({
4963
+ kind,
4964
+ commit
4965
+ });
4966
+ };
4967
+ const copy = (text) => {
4968
+ setMenu(null);
4969
+ navigator.clipboard.writeText(text).then(() => pushToast(t("hover.copied"), "success"), () => pushToast(t("errorGeneric", { message: t("hover.copied") }), "error"));
4970
+ };
4971
+ const nameIssue = draft.trim() === "" ? "empty" : branchNameIssue(draft.trim());
4972
+ const duplicateBranch = dialog !== null && dialog.kind === "branch" && branches.includes(draft.trim());
4973
+ const dialogError = nameIssue === "empty" ? t("menuNewBranchPlaceholder") : nameIssue !== null ? t("menuNewBranchBad") : duplicateBranch ? t("menuNewBranchExists") : void 0;
4974
+ const submitDialog = () => {
4975
+ if (dialog === null || nameIssue !== null || duplicateBranch) return;
4976
+ const name = draft.trim();
4977
+ if (dialog.kind === "branch") onCreateBranch(name, dialog.commit.hash);
4978
+ else onCreateTag(name, dialog.commit.hash, message.trim());
4979
+ setDialog(null);
4980
+ setDraft("");
4981
+ setMessage("");
4982
+ };
4983
+ /** Commit a pending author needle — Enter or blur, never per keystroke: the
4984
+ * filter is a server-side walk, and one request per character would queue
4985
+ * dozens of `git log` runs behind a single word. */
4986
+ const commitAuthor = () => {
4987
+ const author = authorDraft.trim();
4988
+ if (author === filters.author) return;
4989
+ onFilters({
4990
+ ...filters,
4991
+ author
4992
+ });
4993
+ };
4994
+ const filterBar = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4995
+ className: GitHistoryPanel_module_default.historyFilters,
4996
+ children: [
4997
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4998
+ type: "text",
4999
+ className: GitHistoryPanel_module_default.filterInput,
5000
+ value: authorDraft,
5001
+ placeholder: t("panel.historyAuthor"),
5002
+ "aria-label": t("panel.historyAuthor"),
5003
+ onChange: (event) => setAuthorDraft(event.target.value),
5004
+ onBlur: commitAuthor,
5005
+ onKeyDown: (event) => {
5006
+ if (event.key === "Enter") {
5007
+ event.preventDefault();
5008
+ commitAuthor();
5009
+ }
5010
+ }
5011
+ }),
5012
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
5013
+ className: GitHistoryPanel_module_default.filterSelect,
5014
+ value: filters.branch,
5015
+ "aria-label": t("panel.historyBranch"),
5016
+ onChange: (event) => onFilters({
5017
+ ...filters,
5018
+ branch: event.target.value
5019
+ }),
5020
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5021
+ value: "",
5022
+ children: t("panel.historyAllRefs")
5023
+ }), branches.map((name) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5024
+ value: name,
5025
+ children: name
5026
+ }, name))]
5027
+ }),
5028
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
5029
+ className: GitHistoryPanel_module_default.filterSelect,
5030
+ value: filters.range,
5031
+ "aria-label": t("panel.historyTime"),
5032
+ onChange: (event) => onFilters({
5033
+ ...filters,
5034
+ range: event.target.value
5035
+ }),
5036
+ children: [
5037
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5038
+ value: "all",
5039
+ children: t("panel.rangeAll")
5040
+ }),
5041
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5042
+ value: "day",
5043
+ children: t("panel.rangeDay")
5044
+ }),
5045
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5046
+ value: "week",
5047
+ children: t("panel.rangeWeek")
5048
+ }),
5049
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
5050
+ value: "custom",
5051
+ children: t("panel.rangeCustom")
5052
+ })
5053
+ ]
5054
+ }),
5055
+ filters.range === "custom" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
5056
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5057
+ type: "date",
5058
+ className: GitHistoryPanel_module_default.filterDate,
5059
+ value: filters.customSince,
5060
+ "aria-label": t("panel.rangeSince"),
5061
+ onChange: (event) => onFilters({
5062
+ ...filters,
5063
+ customSince: event.target.value
5064
+ })
5065
+ }),
5066
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5067
+ className: GitHistoryPanel_module_default.filterDash,
5068
+ "aria-hidden": "true",
5069
+ children: "–"
5070
+ }),
5071
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5072
+ type: "date",
5073
+ className: GitHistoryPanel_module_default.filterDate,
5074
+ value: filters.customUntil,
5075
+ "aria-label": t("panel.rangeUntil"),
5076
+ onChange: (event) => onFilters({
5077
+ ...filters,
5078
+ customUntil: event.target.value
5079
+ })
5080
+ })
5081
+ ] }) : null
5082
+ ]
5083
+ });
5084
+ const rows = commits.map((commit, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
5085
+ label: () => tooltipLabel(commit),
5086
+ side: "bottom",
5087
+ portal: true,
5088
+ maxWidth: 380,
5089
+ delayMs: 250,
5090
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommitRow, {
5091
+ commit,
5092
+ graph: graph[index] ?? {
5093
+ lane: 0,
5094
+ lanes: 1,
5095
+ edges: []
5096
+ },
5097
+ graphWidth: bandWidth,
5098
+ open: expanded === commit.hash,
5099
+ files: filesByHash[commit.hash],
5100
+ t,
5101
+ onToggle,
5102
+ onMenu: openMenu
5103
+ })
5104
+ }, commit.hash));
5105
+ const dialogTitle = dialog === null ? "" : dialog.kind === "branch" ? t("createFlyAskFrom", { branch: dialog.commit.short }) : t("panel.tagTitle");
4589
5106
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
4590
5107
  className: `${GitHistoryPanel_module_default.section} ${GitHistoryPanel_module_default.sectionFill} ${GitHistoryPanel_module_default.sectionHistory}`,
4591
5108
  "aria-label": t("panel.history"),
4592
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4593
- className: GitHistoryPanel_module_default.historyHead,
4594
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
4595
- className: GitHistoryPanel_module_default.sectionTitle,
4596
- children: t("panel.history")
4597
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4598
- className: GitHistoryPanel_module_default.searchBox,
4599
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4600
- "aria-hidden": "true",
4601
- children: "⌕"
4602
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4603
- type: "text",
4604
- className: GitHistoryPanel_module_default.searchInput,
4605
- value: query,
4606
- placeholder: t("panel.historySearch"),
4607
- "aria-label": t("panel.historySearch"),
4608
- onChange: (event) => setQuery(event.target.value)
4609
- })]
4610
- })]
4611
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4612
- className: `${GitHistoryPanel_module_default.sectionBody} ${GitHistoryPanel_module_default.sectionBodyScroll}`,
4613
- children: [loadError !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4614
- className: GitHistoryPanel_module_default.errorText,
4615
- children: loadError
4616
- }) : !loaded && loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4617
- className: GitHistoryPanel_module_default.empty,
4618
- children: t("manager.loading")
4619
- }) : visible.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4620
- className: GitHistoryPanel_module_default.empty,
4621
- children: needle === "" ? t("panel.noCommits") : t("panel.noMatches")
4622
- }) : visible.map((commit, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
4623
- label: () => tooltipLabel(commit),
4624
- side: "bottom",
4625
- portal: true,
4626
- maxWidth: 380,
4627
- delayMs: 250,
4628
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommitRow, {
4629
- commit,
4630
- first: index === 0,
4631
- last: index === visible.length - 1,
4632
- open: expanded === commit.hash,
4633
- files: filesByHash[commit.hash],
4634
- t,
4635
- onToggle,
4636
- timeLabelFor
4637
- })
4638
- }, commit.hash)), hasMore && visible.length === commits.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4639
- className: GitHistoryPanel_module_default.moreRow,
4640
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4641
- type: "button",
4642
- className: GitHistoryPanel_module_default.act,
4643
- disabled: loadingMore,
4644
- onClick: onLoadMore,
4645
- children: t("panel.loadMore")
4646
- })
4647
- }) : null]
4648
- })]
5109
+ children: [
5110
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5111
+ className: GitHistoryPanel_module_default.historyHead,
5112
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
5113
+ className: GitHistoryPanel_module_default.sectionTitle,
5114
+ children: t("panel.history")
5115
+ }), filterBar]
5116
+ }),
5117
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5118
+ className: `${GitHistoryPanel_module_default.sectionBody} ${GitHistoryPanel_module_default.sectionBodyScroll}`,
5119
+ style: { "--graph-w": `${String(bandWidth)}px` },
5120
+ children: [loadError !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5121
+ className: GitHistoryPanel_module_default.errorText,
5122
+ children: loadError
5123
+ }) : !loaded && loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5124
+ className: GitHistoryPanel_module_default.empty,
5125
+ children: t("manager.loading")
5126
+ }) : commits.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5127
+ className: GitHistoryPanel_module_default.empty,
5128
+ children: t("panel.noCommits")
5129
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5130
+ className: GitHistoryPanel_module_default.commitList,
5131
+ children: rows
5132
+ }), hasMore ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5133
+ className: GitHistoryPanel_module_default.moreRow,
5134
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5135
+ type: "button",
5136
+ className: GitHistoryPanel_module_default.act,
5137
+ disabled: loadingMore,
5138
+ onClick: onLoadMore,
5139
+ children: t("panel.loadMore")
5140
+ })
5141
+ }) : null]
5142
+ }),
5143
+ menu !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(FloatMenu, {
5144
+ anchor: menu.rect,
5145
+ onClose: () => setMenu(null),
5146
+ children: [
5147
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5148
+ className: GitHistoryPanel_module_default.menuHead,
5149
+ children: menu.commit.subject
5150
+ }),
5151
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5152
+ type: "button",
5153
+ className: GitHistoryPanel_module_default.menuItem,
5154
+ role: "menuitem",
5155
+ onClick: () => copy(menu.commit.hash),
5156
+ children: t("panel.commitCopyHash")
5157
+ }),
5158
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5159
+ type: "button",
5160
+ className: GitHistoryPanel_module_default.menuItem,
5161
+ role: "menuitem",
5162
+ onClick: () => copy(menu.commit.subject),
5163
+ children: t("panel.commitCopySubject")
5164
+ }),
5165
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5166
+ type: "button",
5167
+ className: GitHistoryPanel_module_default.menuItem,
5168
+ role: "menuitem",
5169
+ disabled: busy,
5170
+ onClick: () => openDialog("branch", menu.commit),
5171
+ children: t("panel.branchNewFrom")
5172
+ }),
5173
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5174
+ type: "button",
5175
+ className: GitHistoryPanel_module_default.menuItem,
5176
+ role: "menuitem",
5177
+ disabled: busy,
5178
+ onClick: () => openDialog("tag", menu.commit),
5179
+ children: t("panel.branchTag")
5180
+ })
5181
+ ]
5182
+ }) : null,
5183
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
5184
+ open: dialog !== null,
5185
+ onClose: () => setDialog(null),
5186
+ title: dialogTitle,
5187
+ closeLabel: t("close"),
5188
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5189
+ size: "sm",
5190
+ variant: "ghost",
5191
+ onClick: () => setDialog(null),
5192
+ children: t("cancel")
5193
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
5194
+ size: "sm",
5195
+ variant: "primary",
5196
+ disabled: busy || nameIssue !== null || duplicateBranch,
5197
+ onClick: submitDialog,
5198
+ children: t("actionConfirm")
5199
+ })] }),
5200
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5201
+ className: GitHistoryPanel_module_default.formField,
5202
+ children: [
5203
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
5204
+ className: GitHistoryPanel_module_default.formLabel,
5205
+ htmlFor: "git-panel-commit-name",
5206
+ children: dialog !== null && dialog.kind === "tag" ? t("panel.tagName") : t("panel.branchNew")
5207
+ }),
5208
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5209
+ id: "git-panel-commit-name",
5210
+ className: GitHistoryPanel_module_default.formInput,
5211
+ "data-modal-autofocus": true,
5212
+ value: draft,
5213
+ onChange: (event) => setDraft(event.target.value),
5214
+ onKeyDown: (event) => {
5215
+ if (event.key === "Enter") submitDialog();
5216
+ }
5217
+ }),
5218
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5219
+ className: GitHistoryPanel_module_default.formError,
5220
+ children: dialogError ?? ""
5221
+ })
5222
+ ]
5223
+ }), dialog !== null && dialog.kind === "tag" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5224
+ className: GitHistoryPanel_module_default.formField,
5225
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
5226
+ className: GitHistoryPanel_module_default.formLabel,
5227
+ htmlFor: "git-panel-tag-message",
5228
+ children: t("panel.tagMessage")
5229
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5230
+ id: "git-panel-tag-message",
5231
+ className: GitHistoryPanel_module_default.formInput,
5232
+ value: message,
5233
+ onChange: (event) => setMessage(event.target.value),
5234
+ onKeyDown: (event) => {
5235
+ if (event.key === "Enter") submitDialog();
5236
+ }
5237
+ })]
5238
+ }) : null]
5239
+ })
5240
+ ]
4649
5241
  });
4650
5242
  }
4651
5243
  //#endregion
@@ -4907,52 +5499,6 @@ window.__ModuleLoader__.load({
4907
5499
  });
4908
5500
  }
4909
5501
  //#endregion
4910
- //#region src/client/relative-time.ts
4911
- /**
4912
- * Compact relative time for trailing labels, as a structured bucket the
4913
- * renderer localizes ("now"/"5min"/"3h"/"2d"/"4mo"/"1y" in en).
4914
- */
4915
- function relativeTime(updatedAt, now) {
4916
- const MIN = 6e4;
4917
- const HOUR = 36e5;
4918
- const DAY = 864e5;
4919
- const diff = Math.max(0, now - updatedAt);
4920
- if (diff < MIN) return {
4921
- unit: "now",
4922
- n: 0
4923
- };
4924
- if (diff < HOUR) return {
4925
- unit: "minutes",
4926
- n: Math.floor(diff / MIN)
4927
- };
4928
- if (diff < DAY) return {
4929
- unit: "hours",
4930
- n: Math.floor(diff / HOUR)
4931
- };
4932
- if (diff < 30 * DAY) return {
4933
- unit: "days",
4934
- n: Math.floor(diff / DAY)
4935
- };
4936
- if (diff < 365 * DAY) return {
4937
- unit: "months",
4938
- n: Math.floor(diff / (30 * DAY))
4939
- };
4940
- return {
4941
- unit: "years",
4942
- n: Math.floor(diff / (365 * DAY))
4943
- };
4944
- }
4945
- /** Localized compact relative time ("刚刚"/"5分钟" in zh, "now"/"5min" in en). */
4946
- function timeLabel(updatedAt, now, t) {
4947
- const { unit, n } = relativeTime(updatedAt, now);
4948
- if (unit === "now") return t("time.now");
4949
- if (unit === "minutes") return t("time.minutes", { n });
4950
- if (unit === "hours") return t("time.hours", { n });
4951
- if (unit === "days") return t("time.days", { n });
4952
- if (unit === "months") return t("time.months", { n });
4953
- return t("time.years", { n });
4954
- }
4955
- //#endregion
4956
5502
  //#region src/client/GitHistoryPanel.tsx
4957
5503
  /**
4958
5504
  * GitHistoryPanel: the git-management tab for the dsh-better-sidebar right
@@ -4972,6 +5518,9 @@ window.__ModuleLoader__.load({
4972
5518
  *
4973
5519
  * @module git-worktree/client/GitHistoryPanel
4974
5520
  */
5521
+ /** One history page. Kept next to the loader: `hasMore` is decided by
5522
+ * comparing the returned length against it. */
5523
+ const HISTORY_PAGE = 50;
4975
5524
  /** One git failure → its message text (the git stderr rides error.message). */
4976
5525
  function failureText(error) {
4977
5526
  return error instanceof Error ? error.message : String(error);
@@ -4997,11 +5546,15 @@ window.__ModuleLoader__.load({
4997
5546
  const [commitMenuOpen, setCommitMenuOpen] = (0, react.useState)(false);
4998
5547
  const [commitDraft, setCommitDraft] = (0, react.useState)("");
4999
5548
  const [aiBusy, setAiBusy] = (0, react.useState)(false);
5549
+ /** The history card's filters. They live HERE, not in the section, because
5550
+ * they are request parameters: the section renders them and hands every
5551
+ * change up, and the loader below re-walks the log with them. */
5552
+ const [historyFilters, setHistoryFilters] = (0, react.useState)(EMPTY_HISTORY_FILTERS);
5000
5553
  /** Response-sequence guard: a refresh raced against a mutation or a cwd
5001
5554
  * change must never land an older snapshot over a newer one. */
5002
5555
  const seqRef = (0, react.useRef)(0);
5003
5556
  const loadHistoryPage = (0, react.useCallback)(async (skip) => {
5004
- const result = await requestLog(cwd, 50, skip);
5557
+ const result = await requestLog(cwd, historyQueryFor(historyFilters, Date.now(), HISTORY_PAGE, skip));
5005
5558
  if (!result.ok) {
5006
5559
  setHistoryError(result.error);
5007
5560
  return;
@@ -5009,9 +5562,9 @@ window.__ModuleLoader__.load({
5009
5562
  setHistoryError(void 0);
5010
5563
  return {
5011
5564
  commits: result.commits,
5012
- complete: result.commits.length < 50
5565
+ complete: result.commits.length < HISTORY_PAGE
5013
5566
  };
5014
- }, [cwd]);
5567
+ }, [cwd, historyFilters]);
5015
5568
  const loadAll = (0, react.useCallback)(async () => {
5016
5569
  const seq = seqRef.current + 1;
5017
5570
  seqRef.current = seq;
@@ -5301,6 +5854,23 @@ window.__ModuleLoader__.load({
5301
5854
  runGuarded,
5302
5855
  t
5303
5856
  ]);
5857
+ /** Tag one commit. Runs against the repository (a tag is a repository-wide
5858
+ * ref), so the history card can tag a commit that no checkout has. Only
5859
+ * local: pushing tags is a release step, not a side effect of a right-click. */
5860
+ const onCreateTag = (0, react.useCallback)((name, target, message) => {
5861
+ runGuarded(async () => {
5862
+ const result = await requestCreateTag(cwd, name, target, message);
5863
+ if (!result.ok) throw new Error(result.error);
5864
+ pushToast(t("panel.tagDone", { tag: result.tag }), "success");
5865
+ await loadAll();
5866
+ });
5867
+ }, [
5868
+ cwd,
5869
+ loadAll,
5870
+ pushToast,
5871
+ runGuarded,
5872
+ t
5873
+ ]);
5304
5874
  const onToggleCommit = (0, react.useCallback)((hash) => {
5305
5875
  if (expanded === hash) {
5306
5876
  setExpanded(null);
@@ -5351,7 +5921,10 @@ window.__ModuleLoader__.load({
5351
5921
  loadHistoryPage,
5352
5922
  loadingMore
5353
5923
  ]);
5354
- const timeLabelFor = (0, react.useCallback)((date) => timeLabel(date, Date.now(), t), [t]);
5924
+ const onHistoryFilters = (0, react.useCallback)((next) => {
5925
+ setHistoryFilters(next);
5926
+ setExpanded(null);
5927
+ }, []);
5355
5928
  if (statusError !== void 0 && repo === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5356
5929
  className: GitHistoryPanel_module_default.errorText,
5357
5930
  children: t("panel.loadFailed", { message: statusError })
@@ -5420,9 +5993,15 @@ window.__ModuleLoader__.load({
5420
5993
  loadingMore,
5421
5994
  expanded,
5422
5995
  filesByHash,
5996
+ filters: historyFilters,
5997
+ branches: rows.locals.map((row) => row.name),
5998
+ busy: sectionBusy,
5423
5999
  onLoadMore,
5424
6000
  onToggle: onToggleCommit,
5425
- timeLabelFor
6001
+ onFilters: onHistoryFilters,
6002
+ onCreateBranch: onCreate,
6003
+ onCreateTag,
6004
+ pushToast
5426
6005
  })
5427
6006
  ]
5428
6007
  });
@@ -5569,6 +6148,52 @@ window.__ModuleLoader__.load({
5569
6148
  }
5570
6149
  }
5571
6150
  //#endregion
6151
+ //#region src/client/relative-time.ts
6152
+ /**
6153
+ * Compact relative time for trailing labels, as a structured bucket the
6154
+ * renderer localizes ("now"/"5min"/"3h"/"2d"/"4mo"/"1y" in en).
6155
+ */
6156
+ function relativeTime(updatedAt, now) {
6157
+ const MIN = 6e4;
6158
+ const HOUR = 36e5;
6159
+ const DAY = 864e5;
6160
+ const diff = Math.max(0, now - updatedAt);
6161
+ if (diff < MIN) return {
6162
+ unit: "now",
6163
+ n: 0
6164
+ };
6165
+ if (diff < HOUR) return {
6166
+ unit: "minutes",
6167
+ n: Math.floor(diff / MIN)
6168
+ };
6169
+ if (diff < DAY) return {
6170
+ unit: "hours",
6171
+ n: Math.floor(diff / HOUR)
6172
+ };
6173
+ if (diff < 30 * DAY) return {
6174
+ unit: "days",
6175
+ n: Math.floor(diff / DAY)
6176
+ };
6177
+ if (diff < 365 * DAY) return {
6178
+ unit: "months",
6179
+ n: Math.floor(diff / (30 * DAY))
6180
+ };
6181
+ return {
6182
+ unit: "years",
6183
+ n: Math.floor(diff / (365 * DAY))
6184
+ };
6185
+ }
6186
+ /** Localized compact relative time ("刚刚"/"5分钟" in zh, "now"/"5min" in en). */
6187
+ function timeLabel(updatedAt, now, t) {
6188
+ const { unit, n } = relativeTime(updatedAt, now);
6189
+ if (unit === "now") return t("time.now");
6190
+ if (unit === "minutes") return t("time.minutes", { n });
6191
+ if (unit === "hours") return t("time.hours", { n });
6192
+ if (unit === "days") return t("time.days", { n });
6193
+ if (unit === "months") return t("time.months", { n });
6194
+ return t("time.years", { n });
6195
+ }
6196
+ //#endregion
5572
6197
  //#region src/client/concurrency.ts
5573
6198
  /**
5574
6199
  * Bounded-concurrency map for the browser half's fan-out calls.
@@ -6747,6 +7372,23 @@ window.__ModuleLoader__.load({
6747
7372
  "panel.createDone": "Created branch {branch}",
6748
7373
  "panel.history": "Commit history",
6749
7374
  "panel.historySearch": "Search commits / authors",
7375
+ "panel.historyBranch": "Branch",
7376
+ "panel.historyAllRefs": "All branches",
7377
+ "panel.historyAuthor": "Author",
7378
+ "panel.historyTime": "Time",
7379
+ "panel.rangeAll": "All time",
7380
+ "panel.rangeDay": "Last 24 hours",
7381
+ "panel.rangeWeek": "Last 7 days",
7382
+ "panel.rangeCustom": "Custom range",
7383
+ "panel.rangeSince": "From",
7384
+ "panel.rangeUntil": "To",
7385
+ "panel.commitCopyHash": "Copy hash",
7386
+ "panel.commitCopySubject": "Copy message",
7387
+ "panel.branchTag": "Tag this commit…",
7388
+ "panel.tagTitle": "New tag at {branch}",
7389
+ "panel.tagName": "Tag name",
7390
+ "panel.tagMessage": "Message (optional)",
7391
+ "panel.tagDone": "Tagged {tag}",
6750
7392
  "panel.noCommits": "This repository has no commits yet",
6751
7393
  "panel.noMatches": "No matching commits",
6752
7394
  "panel.loadMore": "Load more",
@@ -6948,6 +7590,23 @@ window.__ModuleLoader__.load({
6948
7590
  "panel.createDone": "已创建分支 {branch}",
6949
7591
  "panel.history": "提交历史",
6950
7592
  "panel.historySearch": "搜索提交 / 作者",
7593
+ "panel.historyBranch": "分支",
7594
+ "panel.historyAllRefs": "所有分支",
7595
+ "panel.historyAuthor": "提交人",
7596
+ "panel.historyTime": "时间",
7597
+ "panel.rangeAll": "全部",
7598
+ "panel.rangeDay": "近 24 小时",
7599
+ "panel.rangeWeek": "近 7 天",
7600
+ "panel.rangeCustom": "自定义范围",
7601
+ "panel.rangeSince": "从",
7602
+ "panel.rangeUntil": "到",
7603
+ "panel.commitCopyHash": "复制哈希",
7604
+ "panel.commitCopySubject": "复制消息",
7605
+ "panel.branchTag": "添加标签…",
7606
+ "panel.tagTitle": "在 {branch} 上新标签",
7607
+ "panel.tagName": "标签名称",
7608
+ "panel.tagMessage": "消息(可选)",
7609
+ "panel.tagDone": "已标记 {tag}",
6951
7610
  "panel.noCommits": "仓库还没有提交",
6952
7611
  "panel.noMatches": "没有匹配的提交",
6953
7612
  "panel.loadMore": "加载更多",