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.
Files changed (3) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/lib/client.js +10 -188
  3. 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 "next" row — the node half's single highest-priority suggestion,
797
- // { command, why } or absent (clean + synced + nothing failing). Clicking
798
- // copies the command; it is the one actionable affordance the card
799
- // carries, and it costs no server surface: the command runs in the
800
- // user's own terminal, never here. Per-open state: the copied ack
801
- // resets the next time the card opens, which is the honest lifetime for it.
802
- const next = data.next;
803
- if (next !== void 0 && next !== null && typeof next.why === "string") {
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
- Menu === void 0 || actions.length === 0
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, buildActions, actionText, ghSkillActions };
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.0",
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",