dsh-git-badge 0.14.0 → 0.14.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 +13 -0
- package/lib/client.js +10 -188
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,19 @@ npm. 0.6.0 is the first release recorded here.
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [0.14.1] - 2026-09-15
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- **The chip's hover card top line is now an `action:` row** — the gh skill
|
|
18
|
+
invocation the checkout justifies (e.g. `/gh push`), in the card's ordinary
|
|
19
|
+
label/value layout, replacing the next-step line's why-plus-copy-chip. The
|
|
20
|
+
actionable surface remains the (+) menu's `/gh` picker; the card row is its
|
|
21
|
+
read-only pointer.
|
|
22
|
+
- **The branch pull-down is removed from the chip.** The (+) menu's `/gh`
|
|
23
|
+
picker supersedes it as the actionable surface, and the chip returns to
|
|
24
|
+
mark + branch + PR token + notice.
|
|
25
|
+
|
|
13
26
|
## [0.14.0] - 2026-09-15
|
|
14
27
|
|
|
15
28
|
### Added
|
package/lib/client.js
CHANGED
|
@@ -230,52 +230,6 @@ window.__ModuleLoader__.load({
|
|
|
230
230
|
return "ok";
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
/**
|
|
234
|
-
* The git action list both actionable surfaces render — the `!` trigger
|
|
235
|
-
* menu in the input and the branch pull-down on the chip. State-filtered:
|
|
236
|
-
* the server's `next` suggestion leads, then only actions the current
|
|
237
|
-
* status justifies. Deduped by command (the next step already covers one
|
|
238
|
-
* of these when it agrees) and capped, because a menu of twelve is a
|
|
239
|
-
* reference manual, not an action menu.
|
|
240
|
-
*/
|
|
241
|
-
function buildActions(info) {
|
|
242
|
-
if (info === void 0 || info === null || info.git !== true) return [];
|
|
243
|
-
const actions = [];
|
|
244
|
-
const seen = new Set();
|
|
245
|
-
const add = (command, why) => {
|
|
246
|
-
if (typeof command !== "string" || command === "" || seen.has(command)) return;
|
|
247
|
-
seen.add(command);
|
|
248
|
-
actions.push({ name: command, label: command, description: why, command, why });
|
|
249
|
-
};
|
|
250
|
-
const next = info.next;
|
|
251
|
-
if (next !== void 0 && next !== null && typeof next.command === "string") {
|
|
252
|
-
add(next.command, next.why);
|
|
253
|
-
}
|
|
254
|
-
const ahead = info.ahead || 0;
|
|
255
|
-
const behind = info.behind || 0;
|
|
256
|
-
const staged = info.stagedFiles || 0;
|
|
257
|
-
const unstaged = info.unstagedFiles || 0;
|
|
258
|
-
const untracked = info.untrackedFiles || 0;
|
|
259
|
-
if (behind > 0) add("git pull --ff-only", behind + " behind " + (info.upstream ?? "upstream"));
|
|
260
|
-
if (ahead > 0) add("git push", ahead + " ahead of " + (info.upstream ?? "upstream"));
|
|
261
|
-
if (staged > 0) add("git commit", staged + " staged");
|
|
262
|
-
if (unstaged > 0) add("git add -p && git commit", unstaged + " unstaged");
|
|
263
|
-
if (unstaged === 0 && untracked > 0) add("git add -A && git commit", untracked + " untracked");
|
|
264
|
-
if ((info.stashCount || 0) > 0) add("git stash list", info.stashCount + " stashed");
|
|
265
|
-
const pr = info.pr;
|
|
266
|
-
if (pr !== void 0 && pr !== null && pr.number !== void 0) {
|
|
267
|
-
if (pr.state === "failing") add("gh pr checks " + pr.number + " --watch", "checks failing on #" + pr.number);
|
|
268
|
-
add("gh pr view " + pr.number, "open pull request #" + pr.number);
|
|
269
|
-
} else if (ahead === 0 && behind === 0 && info.upstream !== void 0 && staged + unstaged + untracked === 0) {
|
|
270
|
-
add("gh pr create", "branch is pushed and has no pull request");
|
|
271
|
-
}
|
|
272
|
-
return actions.slice(0, 6);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/** The input text a picked action becomes: the command, then why as a comment. */
|
|
276
|
-
function actionText(action) {
|
|
277
|
-
return "!" + action.command + " # " + action.why;
|
|
278
|
-
}
|
|
279
233
|
|
|
280
234
|
/**
|
|
281
235
|
* The /gh picker's sub-actions — the skill invocations the checkout
|
|
@@ -710,65 +664,6 @@ window.__ModuleLoader__.load({
|
|
|
710
664
|
const CARD_LABEL = { color: "var(--dsw-alias-label-tertiary, #9ea7ad)", flex: "none", minWidth: "62px" };
|
|
711
665
|
const CARD_VALUE = { minWidth: 0, overflowWrap: "anywhere" };
|
|
712
666
|
|
|
713
|
-
/**
|
|
714
|
-
* The "next" row's body: why, then the command as a click-to-copy chip.
|
|
715
|
-
* Clipboard-only by design — no server call, no path exposure; the command
|
|
716
|
-
* lands in the user's clipboard to run in their own terminal. `navigator.
|
|
717
|
-
* clipboard` needs a user gesture (a click is one) and a secure context
|
|
718
|
-
* (localhost is one); a denied write degrades to a no-op and the command
|
|
719
|
-
* is still readable as text.
|
|
720
|
-
*/
|
|
721
|
-
function NextStepRow({ next }) {
|
|
722
|
-
const [copied, setCopied] = react.useState(false);
|
|
723
|
-
const copy = () => {
|
|
724
|
-
if (typeof next.command !== "string") return;
|
|
725
|
-
try {
|
|
726
|
-
void navigator.clipboard.writeText(next.command).then(
|
|
727
|
-
() => {
|
|
728
|
-
setCopied(true);
|
|
729
|
-
setTimeout(() => setCopied(false), 1500);
|
|
730
|
-
},
|
|
731
|
-
() => void 0,
|
|
732
|
-
);
|
|
733
|
-
} catch {
|
|
734
|
-
void 0;
|
|
735
|
-
}
|
|
736
|
-
};
|
|
737
|
-
return react_jsx_runtime.jsxs(
|
|
738
|
-
"span",
|
|
739
|
-
{
|
|
740
|
-
style: { ...CARD_VALUE, display: "inline-flex", alignItems: "center", gap: "6px", flexWrap: "wrap" },
|
|
741
|
-
children: [
|
|
742
|
-
react_jsx_runtime.jsx("span", { children: next.why }),
|
|
743
|
-
typeof next.command === "string"
|
|
744
|
-
? react_jsx_runtime.jsx(
|
|
745
|
-
"button",
|
|
746
|
-
{
|
|
747
|
-
"aria-label": "Copy command: " + next.command,
|
|
748
|
-
title: "Copy command: " + next.command,
|
|
749
|
-
onClick: copy,
|
|
750
|
-
style: {
|
|
751
|
-
cursor: "pointer",
|
|
752
|
-
color: "inherit",
|
|
753
|
-
background: "none",
|
|
754
|
-
border: "1px solid var(--dsw-alias-border-secondary, #d0d7de)",
|
|
755
|
-
borderRadius: "4px",
|
|
756
|
-
fontSize: "11px",
|
|
757
|
-
lineHeight: "16px",
|
|
758
|
-
padding: "0 5px",
|
|
759
|
-
flex: "none",
|
|
760
|
-
fontFamily: "monospace"
|
|
761
|
-
},
|
|
762
|
-
children: copied ? "copied" : next.command
|
|
763
|
-
},
|
|
764
|
-
"cmd"
|
|
765
|
-
)
|
|
766
|
-
: null
|
|
767
|
-
]
|
|
768
|
-
},
|
|
769
|
-
"next-body"
|
|
770
|
-
);
|
|
771
|
-
}
|
|
772
667
|
|
|
773
668
|
/**
|
|
774
669
|
* Hover card body for the input chip — INPUT CHIP ONLY. Pure presentation
|
|
@@ -793,17 +688,14 @@ window.__ModuleLoader__.load({
|
|
|
793
688
|
}, label)
|
|
794
689
|
);
|
|
795
690
|
};
|
|
796
|
-
// The "
|
|
797
|
-
//
|
|
798
|
-
//
|
|
799
|
-
//
|
|
800
|
-
//
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
rows.push(
|
|
805
|
-
react_jsx_runtime.jsxs(NextStepRow, { next }, "next")
|
|
806
|
-
);
|
|
691
|
+
// The "action" row — the top line, in the card's ordinary row layout:
|
|
692
|
+
// the gh skill invocation the checkout justifies, derived from the same
|
|
693
|
+
// state the /gh picker uses (first sub-action wins). Plain text, no
|
|
694
|
+
// affordance: the actionable surface is the (+) menu's /gh picker, and
|
|
695
|
+
// this row is the read-only pointer to it.
|
|
696
|
+
const topAction = ghSkillActions(data)[0];
|
|
697
|
+
if (topAction !== void 0) {
|
|
698
|
+
add("action:", "/gh " + topAction.args);
|
|
807
699
|
}
|
|
808
700
|
add("branch", info.branch);
|
|
809
701
|
add("upstream", info.upstream === void 0 ? "none configured" : info.upstream);
|
|
@@ -894,16 +786,8 @@ window.__ModuleLoader__.load({
|
|
|
894
786
|
// re-render that no fetch will schedule, so the × bumps a counter here.
|
|
895
787
|
const [, bumpNotice] = react.useState(0);
|
|
896
788
|
const seamNotice = seamHint !== null && !seamNoticeDismissed ? seamHint : null;
|
|
897
|
-
// The branch pull-down (model-selection pattern): a chevron opens the
|
|
898
|
-
// shell Menu of state-filtered git actions; picking COPIES the command —
|
|
899
|
-
// the composer has no public insert API (see the trigger source in
|
|
900
|
-
// apply() for the insertion-capable surface), so this surface's contract
|
|
901
|
-
// is paste-and-send, the same one the hover card's chip has always had.
|
|
902
|
-
const [menuOpen, setMenuOpen] = react.useState(false);
|
|
903
|
-
const [copied, setCopied] = react.useState(false);
|
|
904
789
|
const detail = useGitStatus(target, { pr: true, detail: true, enabled: hovered });
|
|
905
790
|
if (info === void 0 || info.git !== true) return null;
|
|
906
|
-
const actions = buildActions({ ...info, ...(detail ?? {}) });
|
|
907
791
|
|
|
908
792
|
// The status mark wrapped in the chip's hover card — the card's ONLY
|
|
909
793
|
// anchor. Pointer rest here (and only here) enables the detail=1 fetch,
|
|
@@ -962,69 +846,7 @@ window.__ModuleLoader__.load({
|
|
|
962
846
|
// stays on the whole chip — it is about the badge, not the mark.
|
|
963
847
|
hoverableMark,
|
|
964
848
|
|
|
965
|
-
|
|
966
|
-
? null
|
|
967
|
-
: react_jsx_runtime.jsx(Menu, {
|
|
968
|
-
key: "actions",
|
|
969
|
-
open: menuOpen,
|
|
970
|
-
onClose: () => setMenuOpen(false),
|
|
971
|
-
items: actions.map((action) => ({ id: action.command, label: action.label, description: action.description })),
|
|
972
|
-
onSelect: (picked) => {
|
|
973
|
-
setMenuOpen(false);
|
|
974
|
-
// the shell may hand back the id or the whole item — take either
|
|
975
|
-
const id = typeof picked === "string" ? picked : picked?.id;
|
|
976
|
-
const action = actions.find((entry) => entry.command === id);
|
|
977
|
-
console.info("[dsh-git-badge] pull-down pick:", id, "->", action === void 0 ? "NOT FOUND" : actionText(action));
|
|
978
|
-
if (action === void 0) return;
|
|
979
|
-
try {
|
|
980
|
-
void navigator.clipboard.writeText(actionText(action)).then(() => {
|
|
981
|
-
setCopied(true);
|
|
982
|
-
setTimeout(() => setCopied(false), 1200);
|
|
983
|
-
}, () => void 0);
|
|
984
|
-
} catch {
|
|
985
|
-
void 0;
|
|
986
|
-
}
|
|
987
|
-
},
|
|
988
|
-
align: "start",
|
|
989
|
-
portal: true,
|
|
990
|
-
anchor: react_jsx_runtime.jsxs(
|
|
991
|
-
"button",
|
|
992
|
-
{
|
|
993
|
-
type: "button",
|
|
994
|
-
"aria-haspopup": "menu",
|
|
995
|
-
"aria-expanded": menuOpen,
|
|
996
|
-
"aria-label": "Git actions for " + info.branch,
|
|
997
|
-
title: copied ? "copied — paste in your terminal" : "git actions",
|
|
998
|
-
onClick: () => setMenuOpen((value) => !value),
|
|
999
|
-
// the metrics of the row's own selectors (the access/model
|
|
1000
|
-
// pull-downs): inline-flex, centered on the 24px line, no
|
|
1001
|
-
// border or padding, icon flex-none so the chevron reads as an
|
|
1002
|
-
// affordance, not a second label
|
|
1003
|
-
style: {
|
|
1004
|
-
cursor: "pointer",
|
|
1005
|
-
color: "inherit",
|
|
1006
|
-
background: "none",
|
|
1007
|
-
border: "none",
|
|
1008
|
-
display: "inline-flex",
|
|
1009
|
-
alignItems: "center",
|
|
1010
|
-
justifyContent: "center",
|
|
1011
|
-
fontSize: "12px",
|
|
1012
|
-
lineHeight: "24px",
|
|
1013
|
-
padding: "0",
|
|
1014
|
-
flex: "none"
|
|
1015
|
-
},
|
|
1016
|
-
children: [
|
|
1017
|
-
copied
|
|
1018
|
-
? "\u2713"
|
|
1019
|
-
: (primitives !== null && primitives.IconChevronDownOutline14 !== void 0
|
|
1020
|
-
? react_jsx_runtime.jsx(primitives.IconChevronDownOutline14, {})
|
|
1021
|
-
: "\u25BE")
|
|
1022
|
-
]
|
|
1023
|
-
},
|
|
1024
|
-
"actions-anchor"
|
|
1025
|
-
)
|
|
1026
|
-
}),
|
|
1027
|
-
react_jsx_runtime.jsx("span", { key: "text", children: text }),
|
|
849
|
+
react_jsx_runtime.jsx("span", { key: "text", children: text }),
|
|
1028
850
|
prToken === ""
|
|
1029
851
|
? null
|
|
1030
852
|
: prUrl === void 0
|
|
@@ -1313,7 +1135,7 @@ window.__ModuleLoader__.load({
|
|
|
1313
1135
|
// Additive; the host reads apply/inject and ignores the rest. The suite
|
|
1314
1136
|
// drives these to assert the REQUEST contract — which surface asks for the
|
|
1315
1137
|
// expensive extras — without a browser, a fetch or a network.
|
|
1316
|
-
exports.__internals = { targetQuery, formatPrToken, formatFileBreakdown, formatPrDetail, formatCheckoutDetail, worktreeDetail, actionToken,
|
|
1138
|
+
exports.__internals = { targetQuery, formatPrToken, formatFileBreakdown, formatPrDetail, formatCheckoutDetail, worktreeDetail, actionToken, ghSkillActions };
|
|
1317
1139
|
return module.exports;
|
|
1318
1140
|
}
|
|
1319
1141
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-git-badge",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "Git status badges for DeepSeek Harness: an input-row chip on every install, plus sidebar session-row badges (git status, PR/CI action tokens) after one manual step: `npx dsh-git-badge apply`, then restart dsh web. Event-driven freshness over SSE.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|