dsh-git-badge 0.17.0 → 0.17.1
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/CHANGELOG.md +41 -7
- package/README.md +1 -1
- package/lib/client.js +91 -67
- package/lib/index.js +98 -120
- package/package.json +2 -4
- package/seam/apply.js +34 -154
- package/seam/checkout.js +3 -5
- package/seam/patch.js +142 -0
- package/seam/postinstall.js +38 -50
- package/seam/store.js +8 -4
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,31 @@ npm. 0.6.0 is the first release recorded here.
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [0.17.1] - 2026-09-17
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **The branch, file-count and PR hovers open on the FIRST hover.** Those three
|
|
18
|
+
surfaces were only wrapped in the shell's `Tooltip` once the lazy `detail=1`
|
|
19
|
+
payload arrived. That primitive is uncontrolled — it has no `open` prop and
|
|
20
|
+
opens from a `mouseenter` it attaches to its child — so a wrapper mounted after
|
|
21
|
+
the pointer had already arrived could never open for that visit: the first hover
|
|
22
|
+
did nothing, and worked only after leaving and returning (or after hovering the
|
|
23
|
+
status mark had already warmed the cache). All four surfaces are now wrapped
|
|
24
|
+
from the first render, and a label whose detail is still in flight degrades to
|
|
25
|
+
the base response instead of claiming an empty list — the count restates the
|
|
26
|
+
file breakdown, the PR token names the PR. The branch's hover child also stays a
|
|
27
|
+
`span`, so the compare link appearing underneath a resting pointer cannot
|
|
28
|
+
remount it. The status mark's own card was never affected.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- **Internal simplification pass, no user-facing change** (#52): one
|
|
33
|
+
`singleFlight` now backs the status cache and its in-flight coalescing, one
|
|
34
|
+
patch engine (`seam/patch.js`) backs both `seam/apply.js` and
|
|
35
|
+
`seam/postinstall.js`, and the server returns one `nextActions` shape
|
|
36
|
+
(`{actions, next}`) for the row's action list.
|
|
37
|
+
|
|
13
38
|
## [0.17.0] - 2026-09-16
|
|
14
39
|
|
|
15
40
|
### Added
|
|
@@ -278,22 +303,18 @@ npm. 0.6.0 is the first release recorded here.
|
|
|
278
303
|
|
|
279
304
|
## [0.14.1] - 2026-09-15
|
|
280
305
|
|
|
306
|
+
### Changed
|
|
307
|
+
|
|
281
308
|
- **Skill alignment:** the `gh` skill gains an explicit handoff — one verb per
|
|
282
309
|
invocation is its whole job; the multi-step PR lifecycle (review, merge,
|
|
283
310
|
verify, cleanup) belongs to the `gh-pr` skill. The `gh-pr` skill states the
|
|
284
311
|
mirror scope: one-shot verbs are `gh`'s, lifecycle is its own. The catalog
|
|
285
312
|
stays global-only; repo conventions stay in `AGENTS.md`.
|
|
286
|
-
|
|
287
313
|
- **The hover card is 33% wider** (360px → 480px) and its `action:` row now
|
|
288
314
|
carries a trailing what-comment in git-comment convention — e.g.
|
|
289
315
|
`action: /gh push # push local commits to the remote` — so the row says
|
|
290
316
|
what the invocation does, not just which invocation it is. The picker in the
|
|
291
317
|
(+) menu shares the same descriptions.
|
|
292
|
-
|
|
293
|
-
## [0.14.1] - 2026-09-15
|
|
294
|
-
|
|
295
|
-
### Changed
|
|
296
|
-
|
|
297
318
|
- **The chip's hover card top line is now an `action:` row** — the gh skill
|
|
298
319
|
invocation the checkout justifies (e.g. `/gh push`), in the card's ordinary
|
|
299
320
|
label/value layout, replacing the next-step line's why-plus-copy-chip. The
|
|
@@ -685,7 +706,20 @@ npm. 0.6.0 is the first release recorded here.
|
|
|
685
706
|
- `detail=1` is annotated as having no consumer yet (the hover card remains
|
|
686
707
|
pending). Its comment previously claimed `log -1` while the code ran `log -3`.
|
|
687
708
|
|
|
688
|
-
[Unreleased]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/compare/v0.
|
|
709
|
+
[Unreleased]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/compare/v0.17.0...HEAD
|
|
710
|
+
[0.17.0]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/releases/tag/v0.17.0
|
|
711
|
+
[0.16.1]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/releases/tag/v0.16.1
|
|
712
|
+
[0.16.0]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/releases/tag/v0.16.0
|
|
713
|
+
[0.15.0]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/releases/tag/v0.15.0
|
|
714
|
+
[0.14.1]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/releases/tag/v0.14.1
|
|
715
|
+
[0.14.0]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/releases/tag/v0.14.0
|
|
716
|
+
[0.13.0]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/releases/tag/v0.13.0
|
|
717
|
+
[0.12.0]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/releases/tag/v0.12.0
|
|
718
|
+
[0.11.0]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/releases/tag/v0.11.0
|
|
719
|
+
[0.10.1]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/releases/tag/v0.10.1
|
|
720
|
+
[0.10.0]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/releases/tag/v0.10.0
|
|
721
|
+
[0.9.1]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/releases/tag/v0.9.1
|
|
722
|
+
[0.9.0]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/releases/tag/v0.9.0
|
|
689
723
|
[0.8.0]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/releases/tag/v0.8.0
|
|
690
724
|
[0.7.0]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/releases/tag/v0.7.0
|
|
691
725
|
[0.6.0]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/releases/tag/v0.6.0
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Git status badges for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness):
|
|
4
4
|
|
|
5
5
|
- **Input-row chip** — `● main ↑0 ↓2 ✎3` next to the access picker, showing the git state of the workspace the current conversation is attached to. Works on every install.
|
|
6
|
-
- **Hover card** — rest a pointer on the chip for what won't fit: the file breakdown (`✎3` split into staged / unstaged / untracked), the last
|
|
6
|
+
- **Hover card** — rest a pointer on the chip for what won't fit: the file breakdown (`✎3` split into staged / unstaged / untracked), the last ten commits, and the stash. Fetched only on hover, so the everyday badge pays nothing for it. Works on every install.
|
|
7
7
|
- **PR / CI token** — `● main ↑0 ↓2 PR#142 ✗` when the branch has a GitHub pull request, read through your own `gh` CLI (the plugin never handles a credential). The token **links to the PR** — it opens in a new tab, and underlines when you hover or focus it. Invisible when `gh` cannot answer: not installed, not logged in, no GitHub remote, offline or rate-limited all render the unchanged chip, and a token with no URL to link to stays plain text.
|
|
8
8
|
- **Worktree-aware** — a conversation's directory is fixed when it is created, so a conversation that does its work in a linked `git worktree` would otherwise keep reporting the main checkout. The session registers its checkout (`npx dsh-git-badge-checkout <path>`, run by the git-worktree and `/gh checkout` paths), and the badge describes **that worktree** instead — its branch, counts, tree mark and PR/CI token (`🌳 chore/global-skills-tiering ↑0 ↓2 PR#391 ✓` rather than `● main`). The hover card names the tree in a `worktree` row and adds a `checkout` row with the conversation's own directory, so the main checkout's branch and dirty state stay one hover away. Registrations are advisory: git's own worktree list validates the path, entries expire after 24h, and a workspace-targeted row always reports the checkout the registry owns.
|
|
9
9
|
- **Sidebar session-row action token** — each session row shows the ONE thing that conversation needs from you, or nothing at all: `merge`, `fix CI`, `review`, `resolve`, `pull`, `push`. It is triage rather than status: `merge` appears only when GitHub itself reports `mergeStateStatus: CLEAN`, and every waiting state (draft, blocked, behind, checks still running) stays silent. Hovering the row adds a line naming the checkout and the pull request the action refers to. The token floats to the right of the row, is set heavier than the timestamp beside it, and is coloured by how much it needs you — red for a conflict or a failing build, amber for a requested review, green for `merge`, quiet grey for routine sync. It appears only when the session is **idle** — while a turn is running the row stays quiet, because the checkout is still moving and a suggestion would describe a state that is about to change. Requires the `sidebar.workspaces.sessionRow` seam; without it the plugin degrades to chip-only and logs what's active to the browser console.
|
package/lib/client.js
CHANGED
|
@@ -56,8 +56,17 @@ window.__ModuleLoader__.load({
|
|
|
56
56
|
const Menu = primitives === null ? void 0 : primitives.Menu;
|
|
57
57
|
const rankByName = primitives === null ? void 0 : primitives.rankByName;
|
|
58
58
|
|
|
59
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* Module-level git-status cache. Entries are invalidated by SSE events,
|
|
61
|
+
* never by age — and BOUNDED, because a long-lived tab opens and closes
|
|
62
|
+
* many sessions and nothing else removes an entry. When the cap is
|
|
63
|
+
* exceeded the oldest-INSERTED key is evicted (Map order is insertion,
|
|
64
|
+
* not use); an evicted surface simply refetches, which the in-flight map
|
|
65
|
+
* collapses like any other request.
|
|
66
|
+
*/
|
|
60
67
|
const GIT_CACHE = new Map();
|
|
68
|
+
/** The cache's entry bound — each surface is one key, plus lazy detail keys. */
|
|
69
|
+
const GIT_CACHE_LIMIT = 64;
|
|
61
70
|
/** In-flight fetches per cacheKey: bursts collapse into one request. */
|
|
62
71
|
const GIT_INFLIGHT = new Map();
|
|
63
72
|
/** Slow safety-net poll: refreshes even if the SSE stream is silently dead. */
|
|
@@ -138,7 +147,7 @@ window.__ModuleLoader__.load({
|
|
|
138
147
|
* `options.detail` / `options.pr` ask the node half for the chip's extras
|
|
139
148
|
* (see targetQuery). `options.enabled: false` holds the fetch back
|
|
140
149
|
* entirely — that is what makes the hover card's extra fields LAZY: the
|
|
141
|
-
* chip renders without them and only starts paying for `log -
|
|
150
|
+
* chip renders without them and only starts paying for a `log -10` plus a
|
|
142
151
|
* stash list once a pointer actually rests on it. The hook is still called
|
|
143
152
|
* unconditionally (hooks may not be conditional); the GATE is inside.
|
|
144
153
|
*/
|
|
@@ -173,6 +182,9 @@ window.__ModuleLoader__.load({
|
|
|
173
182
|
if (data !== null && data.error !== void 0 && GIT_CACHE.has(cacheKey)) return;
|
|
174
183
|
if (data !== null && typeof data.workspace === "string") resolvedWorkspace = data.workspace;
|
|
175
184
|
GIT_CACHE.set(cacheKey, { at: Date.now(), data });
|
|
185
|
+
if (GIT_CACHE.size > GIT_CACHE_LIMIT) {
|
|
186
|
+
GIT_CACHE.delete(GIT_CACHE.keys().next().value);
|
|
187
|
+
}
|
|
176
188
|
if (alive) setState({ key: cacheKey, data });
|
|
177
189
|
};
|
|
178
190
|
const load = () => {
|
|
@@ -240,13 +252,10 @@ window.__ModuleLoader__.load({
|
|
|
240
252
|
* `/gh pr` was offered on the default branch. The node half owns the
|
|
241
253
|
* rules and the ranking now; the client adds presentation only — the risk
|
|
242
254
|
* gate for `clean`, and the labels.
|
|
243
|
-
*
|
|
244
|
-
* Falls back to the single `next` for a payload from an older node half.
|
|
245
255
|
*/
|
|
246
256
|
function ghSkillActions(info) {
|
|
247
257
|
if (info === void 0 || info === null || info.git !== true) return [];
|
|
248
|
-
|
|
249
|
-
return info.next === void 0 || info.next === null ? [] : [info.next];
|
|
258
|
+
return Array.isArray(info.actions) ? info.actions : [];
|
|
250
259
|
}
|
|
251
260
|
|
|
252
261
|
/**
|
|
@@ -747,9 +756,15 @@ window.__ModuleLoader__.load({
|
|
|
747
756
|
* columns line up in both by construction), and the card's own width
|
|
748
757
|
* behaviour: full-width lines, nowrap, ellipsis.
|
|
749
758
|
*/
|
|
750
|
-
function PrCommitsList({ info }) {
|
|
759
|
+
function PrCommitsList({ info, pending = false }) {
|
|
751
760
|
const commits = info?.prCommits;
|
|
752
761
|
if (!Array.isArray(commits) || commits.length === 0) {
|
|
762
|
+
// `pending`: the detail payload has not landed yet, so "no commits"
|
|
763
|
+
// would be a false negative — name the pull request the token points
|
|
764
|
+
// at instead, the way the mark's card degrades to the base response.
|
|
765
|
+
if (pending) {
|
|
766
|
+
return react_jsx_runtime.jsx("span", { style: CARD_CONTAINER, children: formatPrDetail(info?.pr) ?? "loading\u2026" });
|
|
767
|
+
}
|
|
753
768
|
return react_jsx_runtime.jsx("span", { style: CARD_CONTAINER, children: "no commits beyond the base branch" });
|
|
754
769
|
}
|
|
755
770
|
return react_jsx_runtime.jsxs("div", { style: CARD_CONTAINER, children: [
|
|
@@ -783,12 +798,13 @@ window.__ModuleLoader__.load({
|
|
|
783
798
|
* the one question the count poses ("what are these?"). One stacked
|
|
784
799
|
* column with sub-headers: UNSTAGED (tracked, edited — valid regardless
|
|
785
800
|
* of the collapsed untracked retry) then UNTRACKED. Reads the same lazy
|
|
786
|
-
* detail payload the mark's card uses
|
|
787
|
-
*
|
|
788
|
-
*
|
|
789
|
-
*
|
|
801
|
+
* detail payload the mark's card uses. The wrapper is always mounted (see
|
|
802
|
+
* withTooltip) and the label degrades while the names are in flight: the
|
|
803
|
+
* COUNTS are already in the base response, so it restates the breakdown
|
|
804
|
+
* rather than claiming an empty list. "no changed files" is reserved for a
|
|
805
|
+
* landed payload that genuinely has no names.
|
|
790
806
|
*/
|
|
791
|
-
function CountNames({ info }) {
|
|
807
|
+
function CountNames({ info, pending = false }) {
|
|
792
808
|
const sections = [
|
|
793
809
|
{
|
|
794
810
|
label: "staged",
|
|
@@ -807,6 +823,11 @@ window.__ModuleLoader__.load({
|
|
|
807
823
|
}
|
|
808
824
|
].filter((section) => Array.isArray(section.names) && section.names.length > 0);
|
|
809
825
|
if (sections.length === 0) {
|
|
826
|
+
// `pending`: the names have not been fetched yet, but the COUNTS have —
|
|
827
|
+
// restate those rather than the false "no changed files".
|
|
828
|
+
if (pending) {
|
|
829
|
+
return react_jsx_runtime.jsx("span", { style: CARD_CONTAINER, children: formatFileBreakdown(info) });
|
|
830
|
+
}
|
|
810
831
|
return react_jsx_runtime.jsx("span", { style: CARD_CONTAINER, children: "no changed files" });
|
|
811
832
|
}
|
|
812
833
|
return react_jsx_runtime.jsxs("div", { style: CARD_CONTAINER, children:
|
|
@@ -1009,6 +1030,24 @@ window.__ModuleLoader__.load({
|
|
|
1009
1030
|
}
|
|
1010
1031
|
|
|
1011
1032
|
//#region composer chip (upstream additive surface: conversation.input.left)
|
|
1033
|
+
/**
|
|
1034
|
+
* The ONE copy of the hover-card degrade rule: wrap a node in the shell's
|
|
1035
|
+
* Tooltip when the primitive exists, and render the node bare otherwise — a
|
|
1036
|
+
* missing card is a smaller failure than a missing badge. `label` is the
|
|
1037
|
+
* thunk the Tooltip calls only when it opens, so the card's element tree
|
|
1038
|
+
* stays out of closed tooltips.
|
|
1039
|
+
*
|
|
1040
|
+
* ALWAYS mount the wrapper — never conditionally, once its data arrives.
|
|
1041
|
+
* The primitive is uncontrolled (it has no `open` prop) and opens on the
|
|
1042
|
+
* CHILD's mouseenter, so a wrapper mounted after the pointer is already
|
|
1043
|
+
* inside can never open for that visit: the pointer has to leave and
|
|
1044
|
+
* return. Gate the LABEL's content instead.
|
|
1045
|
+
*/
|
|
1046
|
+
function withTooltip(node, label, maxWidth) {
|
|
1047
|
+
if (Tooltip === void 0) return node;
|
|
1048
|
+
return react_jsx_runtime.jsx(Tooltip, { side: "top", maxWidth, label, children: node });
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1012
1051
|
/**
|
|
1013
1052
|
* Chip line in the input row: git state of the workspace the CURRENT
|
|
1014
1053
|
* conversation is attached to. It targets the session id and lets the node
|
|
@@ -1044,7 +1083,7 @@ window.__ModuleLoader__.load({
|
|
|
1044
1083
|
const target = sessionId === void 0 ? void 0 : { kind: "session", id: sessionId };
|
|
1045
1084
|
// the PR/CI token is always on the chip, so its fetch is not gated
|
|
1046
1085
|
const info = useGitStatus(target, { pr: true });
|
|
1047
|
-
// The card's extras cost a `log -
|
|
1086
|
+
// The card's extras cost a `log -10` plus a stash list, so they are
|
|
1048
1087
|
// fetched only once a pointer actually RESTS on the chip, then kept
|
|
1049
1088
|
// fresh by the same SSE path. Eagerly asking would add both invocations
|
|
1050
1089
|
// to every refresh — and a refresh fires on every file edit.
|
|
@@ -1069,31 +1108,25 @@ window.__ModuleLoader__.load({
|
|
|
1069
1108
|
// so the extras are paid for exactly when the card that can render them
|
|
1070
1109
|
// is about to open. Defined inside the component: it closes over the
|
|
1071
1110
|
// hovered state that gates the detail fetch.
|
|
1072
|
-
const hoverableMark = (
|
|
1073
|
-
|
|
1111
|
+
const hoverableMark = withTooltip(
|
|
1112
|
+
react_jsx_runtime.jsx("span", {
|
|
1074
1113
|
onPointerEnter: () => setHovered(true),
|
|
1075
1114
|
style: { display: "inline-flex", alignItems: "center", flex: "none", cursor: "default" },
|
|
1076
1115
|
children: react_jsx_runtime.jsx(StatusMark, { info })
|
|
1077
|
-
})
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
// a function label keeps the card's element tree out of every render
|
|
1084
|
-
// until the tooltip actually opens
|
|
1085
|
-
label: () => react_jsx_runtime.jsx(HoverCard, { info, detail }),
|
|
1086
|
-
children: mark
|
|
1087
|
-
});
|
|
1088
|
-
})();
|
|
1116
|
+
}),
|
|
1117
|
+
// a function label keeps the card's element tree out of every render
|
|
1118
|
+
// until the tooltip actually opens
|
|
1119
|
+
() => react_jsx_runtime.jsx(HoverCard, { info, detail }),
|
|
1120
|
+
600 // matches the body cap: the tooltip must never be the clamp
|
|
1121
|
+
);
|
|
1089
1122
|
// the mark is an element now rather than a leading glyph in the string, so
|
|
1090
1123
|
// the SAME StatusMark the sidebar row draws carries the status here too;
|
|
1091
1124
|
// the container's 4px gap supplies the space the emoji's own did
|
|
1092
1125
|
// The branch NAME is its own hover surface: the commits this branch adds
|
|
1093
1126
|
// (BranchCommitsList), sharing the mark's lazy detail fetch. The rest of
|
|
1094
1127
|
// the leading text (operation token) and the count keep their own
|
|
1095
|
-
// surfaces.
|
|
1096
|
-
//
|
|
1128
|
+
// surfaces. The wrapper is always mounted (see withTooltip); without the
|
|
1129
|
+
// Tooltip primitive the name renders plain.
|
|
1097
1130
|
const branchHover = (() => {
|
|
1098
1131
|
// The branch name links to the compare view (base...branch) — the web
|
|
1099
1132
|
// page of exactly what the lineage tooltip lists. Vouched http(s)
|
|
@@ -1106,14 +1139,21 @@ window.__ModuleLoader__.load({
|
|
|
1106
1139
|
// count stays its own surface — it answers a different question.
|
|
1107
1140
|
const sync = formatSync(info);
|
|
1108
1141
|
const label = info.branch + sync;
|
|
1109
|
-
|
|
1142
|
+
// The Tooltip's CHILD must not change element type once the detail
|
|
1143
|
+
// payload lands: the primitive opens on the child's mouseenter and has
|
|
1144
|
+
// no `open` prop, so a node replaced under the pointer may never be
|
|
1145
|
+
// re-entered. The outer span is the stable hover surface; only this
|
|
1146
|
+
// inner node swaps (plain text -> anchor) as the compare URL arrives.
|
|
1147
|
+
const inner = isLink
|
|
1110
1148
|
? react_jsx_runtime.jsx("a", {
|
|
1111
1149
|
href: detail.compareUrl,
|
|
1112
1150
|
target: "_blank",
|
|
1113
1151
|
rel: "noopener noreferrer",
|
|
1114
1152
|
"aria-label": "Compare " + info.branch + " with main on GitHub" + (sync === "" ? "" : ", " + (info.ahead || 0) + " ahead and " + (info.behind || 0) + " behind upstream"),
|
|
1115
|
-
onPointerEnter: () =>
|
|
1153
|
+
onPointerEnter: () => setBranchLinkHover(true),
|
|
1116
1154
|
onPointerLeave: () => setBranchLinkHover(false),
|
|
1155
|
+
// focus joins hover: a keyboard user needs the same underline, and
|
|
1156
|
+
// focus (unlike a pointer) must also start the detail fetch
|
|
1117
1157
|
onFocus: () => { setHovered(true); setBranchLinkHover(true); },
|
|
1118
1158
|
onBlur: () => setBranchLinkHover(false),
|
|
1119
1159
|
style: {
|
|
@@ -1123,22 +1163,17 @@ window.__ModuleLoader__.load({
|
|
|
1123
1163
|
},
|
|
1124
1164
|
children: label
|
|
1125
1165
|
})
|
|
1126
|
-
:
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
if (Tooltip === void 0 || detail === void 0 || detail === null) {
|
|
1132
|
-
// mid-fetch the branch shows plain; the lineage tooltip is
|
|
1133
|
-
// unconditional once the payload lands
|
|
1134
|
-
return branch;
|
|
1135
|
-
}
|
|
1136
|
-
return react_jsx_runtime.jsx(Tooltip, {
|
|
1137
|
-
side: "top",
|
|
1138
|
-
maxWidth: 480,
|
|
1139
|
-
label: () => react_jsx_runtime.jsx(BranchLineage, { info: detail }),
|
|
1140
|
-
children: branch
|
|
1166
|
+
: label;
|
|
1167
|
+
const branch = react_jsx_runtime.jsx("span", {
|
|
1168
|
+
onPointerEnter: () => setHovered(true),
|
|
1169
|
+
style: { cursor: isLink ? "pointer" : "default" },
|
|
1170
|
+
children: inner
|
|
1141
1171
|
});
|
|
1172
|
+
// ALWAYS wrapped. A wrapper mounted only once the lazy detail arrives
|
|
1173
|
+
// cannot open for the visit that fetched it (see the child-stability
|
|
1174
|
+
// note above); until the payload lands the lineage degrades to the base
|
|
1175
|
+
// response, the way the mark's own card already does.
|
|
1176
|
+
return withTooltip(branch, () => react_jsx_runtime.jsx(BranchLineage, { info: detail ?? info }), 480);
|
|
1142
1177
|
})();
|
|
1143
1178
|
const text = formatOperationToken(info);
|
|
1144
1179
|
// The ✎n count is its OWN hover surface: a names-only tooltip (see
|
|
@@ -1153,17 +1188,12 @@ window.__ModuleLoader__.load({
|
|
|
1153
1188
|
style: { cursor: "default" },
|
|
1154
1189
|
children: countText
|
|
1155
1190
|
});
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
return react_jsx_runtime.jsx(
|
|
1162
|
-
side: "top",
|
|
1163
|
-
maxWidth: 480,
|
|
1164
|
-
label: () => react_jsx_runtime.jsx(CountNames, { info: detail }),
|
|
1165
|
-
children: count
|
|
1166
|
-
});
|
|
1191
|
+
// always wrapped, for the same reason as the branch: the primitive
|
|
1192
|
+
// opens on the child's mouseenter, so a wrapper added after the
|
|
1193
|
+
// pointer arrives never opens. While the names are in flight the label
|
|
1194
|
+
// restates the breakdown the base response already carries.
|
|
1195
|
+
const pending = detail === void 0 || detail === null;
|
|
1196
|
+
return withTooltip(count, () => react_jsx_runtime.jsx(CountNames, { info: detail ?? info, pending }), 480);
|
|
1167
1197
|
})();
|
|
1168
1198
|
const prToken = formatPrToken(info);
|
|
1169
1199
|
const prUrl = prLinkUrl(info);
|
|
@@ -1211,16 +1241,10 @@ window.__ModuleLoader__.load({
|
|
|
1211
1241
|
onBlur: () => setLinkHover(false),
|
|
1212
1242
|
children: prToken
|
|
1213
1243
|
});
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
}
|
|
1218
|
-
return react_jsx_runtime.jsx(Tooltip, {
|
|
1219
|
-
side: "top",
|
|
1220
|
-
maxWidth: 480,
|
|
1221
|
-
label: () => react_jsx_runtime.jsx(PrCommitsList, { info: detail }),
|
|
1222
|
-
children: token
|
|
1223
|
-
});
|
|
1244
|
+
// always wrapped (see the branch): while the commits are in flight the
|
|
1245
|
+
// label names the PR state the base response already carries
|
|
1246
|
+
const pending = detail === void 0 || detail === null;
|
|
1247
|
+
return withTooltip(token, () => react_jsx_runtime.jsx(PrCommitsList, { info: detail ?? info, pending }), 480);
|
|
1224
1248
|
})();
|
|
1225
1249
|
const chip = react_jsx_runtime.jsxs("span", {
|
|
1226
1250
|
style: {
|