dsh-git-badge 0.9.1 → 0.10.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 CHANGED
@@ -8,6 +8,42 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
8
8
  Versions before 0.6.0 predate this file; their history is in `git log` and on
9
9
  npm. 0.6.0 is the first release recorded here.
10
10
 
11
+ ## [Unreleased]
12
+
13
+ ## [0.10.1] - 2026-09-14
14
+
15
+ ### Fixed
16
+
17
+ - **Revert backups now live in a stable, user-level store.** They defaulted to
18
+ the patcher's own directory — which for `npx dsh-git-badge` is a cache entry
19
+ keyed by package version, so a backup written by one `apply` was invisible to
20
+ the next `revert`, which then had to refuse ("patched but no backup exists")
21
+ even though the bytes were on disk somewhere. The default is now
22
+ `$DSH_HOME/git-badge-seam` (else `~/.dsh/git-badge-seam`), shared by npx
23
+ runs, the profile bin, the repo wrapper and the postinstall hook;
24
+ `SEAM_DATA_DIR` still overrides. Backups written by older versions to their
25
+ install directories are not migrated — the no-backup refusal now says so and
26
+ names both remaining routes (the repo wrapper's backup, or reinstalling the
27
+ DSH package).
28
+
29
+ ## [0.10.0] - 2026-09-14
30
+
31
+ ### Added
32
+
33
+ - **The install flow now tells you about the one manual step, everywhere it can.**
34
+ A market install activates the input chip immediately, but the sidebar
35
+ session-row badges additionally need the seam patch — and nothing said so.
36
+ Now: the market-listing description names the step; the README leads with the
37
+ two-command install order; a guarded `postinstall` applies the seam at
38
+ install time **when pnpm allows it** (opt-in via `allowBuilds` — DSH's plugin
39
+ installer prints that instruction whenever pnpm blocks a build) and only ever
40
+ in the one safe state (pristine upstream, all anchors resolving — anything
41
+ else just prints, and the hook can never fail an install); and when the seam
42
+ is still absent ~5s after boot, every input chip gains a tooltip naming the
43
+ command — the first message visible in the product rather than the console.
44
+ Restart stays manual by design: an install script restarting the host that is
45
+ installing it is not safe to automate.
46
+
11
47
  ## [0.9.1] - 2026-09-14
12
48
 
13
49
  ### Fixed
package/lib/client.js CHANGED
@@ -695,6 +695,14 @@ window.__ModuleLoader__.load({
695
695
  * always, `detail=1` on hover), because it is the only one that renders
696
696
  * them — see targetQuery.
697
697
  */
698
+ /**
699
+ * Set LATE (see apply) — never at apply() time, because the workspace
700
+ * browser may not have declared the seam yet and a hint set then would
701
+ * lie to patched installs for the whole session. Every chip render reads
702
+ * it, so it reaches the DOM on the next refresh after it is set.
703
+ */
704
+ let seamHint = null;
705
+
698
706
  function ComposerGitChip({ sessionId }) {
699
707
  const target = sessionId === void 0 ? void 0 : { kind: "session", id: sessionId };
700
708
  // the PR/CI token is always on the chip, so its fetch is not gated
@@ -730,6 +738,11 @@ window.__ModuleLoader__.load({
730
738
  whiteSpace: "nowrap",
731
739
  cursor: "default"
732
740
  },
741
+ // the only in-product message a market user gets when the seam is
742
+ // absent: hover the chip you CAN see to learn the one command that
743
+ // turns on the rows you cannot. null until the delayed check in
744
+ // apply() proves the seam never appeared.
745
+ title: seamHint === null ? undefined : seamHint,
733
746
  // the card is fetched for a pointer that RESTS here, not one that
734
747
  // merely crosses the chip
735
748
  onPointerEnter: () => setHovered(true),
@@ -851,6 +864,19 @@ window.__ModuleLoader__.load({
851
864
  // The patcher ships IN this package — no clone needed.
852
865
  "off (seam absent) — run `npx dsh-git-badge apply` to add them, then restart dsh web.")
853
866
  );
867
+ // The chip tooltip can only be set LATE. At apply() time the workspace
868
+ // browser may not have declared the seam yet (the boot race documented
869
+ // above), so claiming absence here would lie to patched installs for the
870
+ // whole session — the exact mistake that made the old one-shot line
871
+ // claim "sidebar rows = off" while five row badges were rendering. Give
872
+ // the composed graph a beat to settle; only a seam that is STILL absent
873
+ // earns the hint, and every chip render after that carries it.
874
+ setTimeout(() => {
875
+ if (rowsReported) return; // seam present; badges rendered — nothing to hint about
876
+ if (ctx.slots.spec("sidebar.workspaces.sessionRow") !== void 0) return;
877
+ seamHint =
878
+ "Session-row badges are off: run `npx dsh-git-badge apply`, then restart dsh web. (Input-chip badges are unaffected.)";
879
+ }, 5000);
854
880
  // The hover card depends on a primitive the SHELL seeds, not on anything
855
881
  // this plugin declares. Report the outcome rather than letting a missing
856
882
  // seed look like a missing feature: the try/catch above deliberately keeps
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-git-badge",
3
- "version": "0.9.1",
4
- "description": "Git status badges for DeepSeek Harness: sidebar session rows (via the sidebar.workspaces.sessionRow seam) and an input-row chip showing the current conversation's workspace git state. Event-driven freshness over SSE.",
3
+ "version": "0.10.1",
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",
7
7
  "bin": {
@@ -13,7 +13,8 @@
13
13
  "./package.json": "./package.json"
14
14
  },
15
15
  "scripts": {
16
- "test": "node --test"
16
+ "test": "node --test",
17
+ "postinstall": "node seam/postinstall.js"
17
18
  },
18
19
  "dsh": {
19
20
  "bundle": {
@@ -50,6 +51,7 @@
50
51
  "lib/client.js",
51
52
  "seam/apply.js",
52
53
  "seam/anchors.js",
54
+ "seam/postinstall.js",
53
55
  "seam/stub-index.js",
54
56
  "cordis.patch.yml",
55
57
  "package.json",
package/seam/apply.js CHANGED
@@ -33,8 +33,10 @@
33
33
  *
34
34
  * Environment:
35
35
  * DSH_INSTALL the DSH package root. Default: `<global npm root>/@deepseek-ai/dsh`.
36
- * SEAM_DATA_DIR where revert backups are written. Default: this script's
37
- * directory (inside the installed package).
36
+ * SEAM_DATA_DIR where revert backups are written. Default:
37
+ * `$DSH_HOME/git-badge-seam` (else `~/.dsh/git-badge-seam`) —
38
+ * a stable, user-level store shared by every way this tool
39
+ * can run. See the note at the definition below.
38
40
  *
39
41
  * Usage: apply.js apply | apply.js revert | apply.js status
40
42
  */
@@ -54,7 +56,17 @@ import {
54
56
  } from "./anchors.js";
55
57
 
56
58
  const HERE = dirname(fileURLToPath(import.meta.url));
57
- const DATA_DIR = process.env.SEAM_DATA_DIR || HERE;
59
+ /**
60
+ * Where revert backups live. A STABLE, user-level directory by default — NOT
61
+ * this script's own directory. For an npx run that directory is a cache entry
62
+ * keyed by package version: a backup written by one `npx dsh-git-badge apply`
63
+ * was invisible to the next `npx dsh-git-badge revert`, which then had to
64
+ * refuse ("patched but no backup exists") even though the bytes were on disk
65
+ * somewhere. Sharing one store across npx runs, the profile bin and the repo
66
+ * wrapper makes the apply→revert round trip reliable for everyone. Override
67
+ * with SEAM_DATA_DIR (the test suite does, to stay hermetic).
68
+ */
69
+ const DATA_DIR = process.env.SEAM_DATA_DIR || join(process.env.DSH_HOME || join(process.env.HOME || "", ".dsh"), "git-badge-seam");
58
70
 
59
71
  // sha256 of the upstream build the anchors were last verified against. ADVISORY:
60
72
  // status prints it for orientation; apply does NOT require it.
@@ -291,9 +303,13 @@ if (verb === "apply") {
291
303
  } else {
292
304
  const r = inspect();
293
305
  if (r.state === "ours-current" || r.state === "ours-stale" || r.state === "ours-corrupt") {
294
- console.error("REFUSING: the installed file is patched but no backup exists, so the");
295
- console.error("original bytes are unknown. Reinstall the package to clear the patch:");
296
- console.error(" npm rebuild -g @deepseek-ai/dsh # or your package manager's equivalent");
306
+ console.error("REFUSING: the installed file is patched but no backup exists in");
307
+ console.error(`${DATA_DIR}, so the original bytes are unknown here. Options:`);
308
+ console.error(" 1. If you applied from a clone of the plugin's repository, run its");
309
+ console.error(" wrapper instead — it keeps its own backup:");
310
+ console.error(" <clone>/seam/apply.sh revert");
311
+ console.error(" 2. Otherwise reinstall the package — pristine files, guaranteed:");
312
+ console.error(" npm i -g @deepseek-ai/dsh@latest");
297
313
  process.exit(1);
298
314
  }
299
315
  console.log("no backup present and nothing of ours installed; client.js left untouched.");
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * postinstall hook — best-effort seam apply at plugin-install time.
4
+ *
5
+ * WHY THIS EXISTS: a market install activates the input chip immediately, but
6
+ * the sidebar session-row badges additionally need the seam patch on the DSH
7
+ * package itself, and nothing in the install flow told the user so. When pnpm
8
+ * allows this script (the user approves it under `allowBuilds` — DSH's plugin
9
+ * installer prints exactly that instruction when pnpm blocks it), the seam is
10
+ * applied right here; otherwise the plugin's boot log and the chip tooltip name
11
+ * the same one manual command.
12
+ *
13
+ * GUARDRAILS — this script may never break an install:
14
+ * - it runs ONLY when the installed DSH client.js is pristine upstream AND
15
+ * every anchor resolves exactly once (`patchable`); any other state —
16
+ * already patched, drifted, upstream-landed, DSH not found — just prints
17
+ * - it never throws: every failure path prints the manual command and exits 0
18
+ * - it cannot restart dsh web; the printed message says to
19
+ *
20
+ * The restart is deliberately manual: the running dsh web process rebuilds its
21
+ * client bundle at boot, and an install script restarting the host that is
22
+ * installing it is not safe to automate.
23
+ */
24
+ import { execSync } from "node:child_process";
25
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
26
+ import { dirname, join } from "node:path";
27
+ import { fileURLToPath } from "node:url";
28
+ import { applyPatch, firstFailure, isOurs, seamPresent } from "./anchors.js";
29
+
30
+ const HERE = dirname(fileURLToPath(import.meta.url));
31
+ const HINT = "Sidebar session-row badges need one manual step: `npx dsh-git-badge apply`, then restart dsh web.";
32
+ /**
33
+ * Same stable backup store apply.js defaults to — the backup this hook takes
34
+ * must be findable by a LATER `npx dsh-git-badge revert` running from a
35
+ * different cache entry. SEAM_DATA_DIR overrides (test suite).
36
+ */
37
+ const DATA_DIR = process.env.SEAM_DATA_DIR || join(process.env.DSH_HOME || join(process.env.HOME || "", ".dsh"), "git-badge-seam");
38
+
39
+ function dshClientPath() {
40
+ try {
41
+ const root = process.env.DSH_INSTALL || join(execSync("npm root -g", { encoding: "utf8" }).trim(), "@deepseek-ai", "dsh");
42
+ return join(root, "node_modules", "@deepseek-ai", "dsh-client-ui-workspace", "lib", "client.js");
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+
48
+ function main() {
49
+ const client = dshClientPath();
50
+ if (client === null || !existsSync(client)) {
51
+ console.log(`[dsh-git-badge] DSH install not found; skipping seam setup. ${HINT}`);
52
+ return;
53
+ }
54
+ // the index stub is copied with the client on apply (see apply.js)
55
+ const index = join(dirname(client), "index.js");
56
+ const text = readFileSync(client, "utf8");
57
+
58
+ // Already ours (any rev): nothing to do here; apply.js upgrade rules govern.
59
+ if (isOurs(text)) {
60
+ console.log("[dsh-git-badge] seam already applied — sidebar session-row badges active after restart.");
61
+ return;
62
+ }
63
+ // Upstream landed the seam: the patch is retired.
64
+ if (seamPresent(text)) {
65
+ console.log("[dsh-git-badge] DSH declares the seam itself — no patch needed.");
66
+ return;
67
+ }
68
+ // Pristine, but the anchors no longer resolve: a DSH update moved anchored
69
+ // code. Never patch half of anything — print, and let a patched anchors.js
70
+ // do it.
71
+ const fail = firstFailure(text);
72
+ if (fail !== null) {
73
+ console.log(`[dsh-git-badge] DSH build drifted from the patch anchors (${fail.name}); skipping. ${HINT}`);
74
+ return;
75
+ }
76
+ // The one state we act on: pristine upstream, anchors resolve.
77
+ mkdirSync(DATA_DIR, { recursive: true });
78
+ copyFileSync(client, join(DATA_DIR, "backup-client.js"));
79
+ copyFileSync(index, join(DATA_DIR, "backup-index.js"));
80
+ writeFileSync(client, applyPatch(text));
81
+ copyFileSync(join(HERE, "stub-index.js"), index);
82
+ console.log("[dsh-git-badge] seam applied — RESTART dsh web to get sidebar session-row badges.");
83
+ }
84
+
85
+ try {
86
+ main();
87
+ } catch (error) {
88
+ // An install hook must never fail the install over the seam.
89
+ console.log(`[dsh-git-badge] seam setup skipped (${String(error && error.message ? error.message : error)}). ${HINT}`);
90
+ }
91
+ process.exit(0);