dsh-git-badge 0.6.0 → 0.8.0

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 ADDED
@@ -0,0 +1,198 @@
1
+ # Changelog
2
+
3
+ All notable changes to this package are documented here.
4
+
5
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
6
+ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ Versions before 0.6.0 predate this file; their history is in `git log` and on
9
+ npm. 0.6.0 is the first release recorded here.
10
+
11
+ ## [Unreleased]
12
+
13
+ ## [0.8.0] - 2026-09-12
14
+
15
+ ### Added
16
+
17
+ - **A hover card on the input chip.** Resting a pointer on the chip now shows what
18
+ the chip has no room for: the file breakdown (`✎3` split into staged / unstaged
19
+ / unmerged / untracked, so *ready to commit* is distinguishable from *not staged
20
+ yet*), the last three commits with their ages, and the stash count. When the
21
+ untracked walk fell back to git's collapsed count the card says `(collapsed)`
22
+ rather than presenting an under-count as exact.
23
+ The card consumes the `detail=1` payload the node half has carried unused since
24
+ 0.6.0, and it is **lazy**: the extra fields cost a `log -3` plus a stash list, so
25
+ they are fetched only once a pointer rests on the chip and then kept fresh over
26
+ the same SSE path. A badge refresh fires on every file edit, and the everyday
27
+ chip still pays for none of it. It uses the shell's own seeded `Tooltip`
28
+ primitive — no new dependency, no seam patch — and if that primitive is ever
29
+ absent the chip renders uncarded rather than not at all. The boot log reports
30
+ which path was taken (`[dsh-git-badge] hover card = on (shell Tooltip
31
+ primitive).`), because the guard that keeps the badge working would otherwise
32
+ make a missing seed look like a missing feature.
33
+ - **A PR / CI token on the input chip.** When the branch has a GitHub pull
34
+ request the chip carries `PR#142` with the rollup state (`✓` passing, `…`
35
+ running, `✗` failing) and a `draft` marker. It reads the PR through the user's
36
+ own `gh` CLI, so the plugin never handles a credential. The rollup is reduced to
37
+ one worst-case state (a single failure outweighs any number of successes), the
38
+ token carries an accessible name naming the state, and the hover card spells out
39
+ the check state, draft and review decision.
40
+ - **The PR token links to the pull request.** When `gh` reports an http(s) URL the
41
+ token is an anchor that opens the PR in a new tab, so this browser tab keeps the
42
+ conversation. It underlines on hover or focus only — the at-rest signal is the
43
+ pointer cursor — and keeps the chip's own colour instead of the browser's link
44
+ blue, so it still reads as one row. Being an anchor, it is also keyboard-reachable
45
+ now, which the text token was not. The protocol is checked on both halves: only
46
+ `http:`/`https:` is carried or rendered, so a payload value can never become a
47
+ `javascript:` href.
48
+ - `?pr=1` on the status route. PR/CI is asked for by the **chip only**, so a
49
+ sidebar that surveys twenty workspaces never spawns twenty `gh` processes. The
50
+ read is TTL-bounded (~90s per repository), refreshed out of band exactly like
51
+ the existing fetch, and notifies subscribers only when the state actually
52
+ changes.
53
+ - **PR/CI degrades to an absent field, never an error.** No `gh`, a logged-out
54
+ `gh`, a non-GitHub remote, an offline network, a rate limit, unparseable output
55
+ and a timed-out call all render the unchanged chip: a caller cannot tell "no PR"
56
+ from "no `gh`", because neither changes what should be displayed.
57
+ - A `runCli` helper the git and `gh` invocations now share, so `gh` inherits the
58
+ same timeout / missing-binary / exit-code semantics (and `GIT_TERMINAL_PROMPT`'s
59
+ analogue, `GH_PROMPT_DISABLED`, keeps an auth prompt from hanging a refresh).
60
+
61
+ ### Changed
62
+
63
+ - The `detail=1` payload has a consumer at last. Its annotation said "no surface
64
+ consumes this yet"; the input chip's hover card is that surface, so the
65
+ branch/last-commits/stash fields are no longer speculative.
66
+ - `config` gains `prStatus` (`"auto"` / `"off"`), `prTtlMs`, `prTimeoutMs` and a
67
+ `prRunner` test seam mirroring `gitRunner`.
68
+
69
+ ## [0.7.0] - 2026-09-12
70
+
71
+ ### Changed
72
+
73
+ - **Both surfaces draw the same status mark.** The input chip no longer leads with
74
+ a 🔴/🟡/🟢 emoji dot: it renders the SAME SVG mark as the sidebar row — a
75
+ filled **circle**, or a **tree** for a linked `git worktree` — so the shape says
76
+ *worktree or not* and the fill says *status*, identically wherever the badge
77
+ appears. The `⑂` worktree glyph is gone with the emoji: the tree shape already
78
+ says "worktree". The mark is drawn rather than typed because 🌳 is a **colour
79
+ emoji** — CSS cannot tint its leaves — and the fill comes from the app's own
80
+ `--dsw-alias-state-{success,warn,error}-primary` tokens, so the mark follows
81
+ light/dark and custom themes, which the hardcoded emoji could not. Neither colour
82
+ nor shape is the only channel: the mark carries an accessible name.
83
+ - **The sidebar row shows status, not the branch.** The row keeps the workspace
84
+ name on the left and floats the status mark to the right, with a worktree's
85
+ directory name beside it; the `|` separator is gone. The branch, sync counts and
86
+ operation token stay on the input chip, the surface scoped to the current
87
+ conversation.
88
+ - **The status endpoint no longer accepts a filesystem path.** The caller now says
89
+ *who it is* and the server resolves the workspace itself: `?session=<id>` for the
90
+ input chip, `?workspace=<id>` for a sidebar row. `?path=` is refused with
91
+ `target-required`; unresolvable ids return `session-not-found` or
92
+ `workspace-not-found`. The path was previously caller-chosen (allowlist-bounded
93
+ and read-only, but still chosen by the caller) on a route that answers without
94
+ GUI authentication.
95
+ - A session is resolved through the workspace registry first, so a conversation
96
+ that is no longer live still renders a badge. The live session's directory is
97
+ consulted only for the window before a new session is attached to its workspace,
98
+ and the registry still validates it.
99
+ - The client half no longer requires the `workspaces` service — declared services
100
+ are now just `["slots"]` — and change notifications carry the workspace id, so
101
+ subscribers match on identity rather than on a path.
102
+
103
+ ### Added
104
+
105
+ - **A client-half test suite, plus submodule coverage.** `test/client.test.mjs`
106
+ loads the real `lib/client.js` behind a stub module loader and renders both
107
+ registered surfaces, so the mark's shape/colour rules, the row's "never show the
108
+ branch" rule, the right-float and the worktree-name suppression are asserted in
109
+ CI rather than eyeballed. The node half gains a real-submodule fixture: a
110
+ submodule is asserted **not** to be a worktree (its gitfile alone must not decide
111
+ it — worktree-ness is the `commondir` marker) while its out-of-tree git dir is
112
+ still watched, like a linked worktree's.
113
+ - **The input chip now says which working tree a conversation is in.** A linked
114
+ `git worktree`'s directory name is appended after the branch —
115
+ `🌳 main hotfix-tree ↑0 ↓2 ✎3` — because several worktrees of one repository
116
+ otherwise render identical chips and the chip carries no other workspace
117
+ identity. The name is dropped when the branch already implies it (`repo-feat-x`
118
+ on `feat-x` shows just the tree), a main checkout appends nothing, and the
119
+ response reports `isWorktree` / `worktreeName` from one stat of the git dir the
120
+ existing `rev-parse` already returned — no extra git invocation. Naming the
121
+ worktree is chip-only, like the operation token. The value is a directory NAME,
122
+ never a path.
123
+ - **A server-side poll fallback for workspaces whose file watcher failed.** A
124
+ workspace whose recursive `fs.watch` could not be established, or which later
125
+ errored, is polled every 5s on a state key (status + refs fingerprint +
126
+ in-progress operation) instead of depending only on the client's 60s fallback.
127
+ - `seam/apply.sh status` — reports patched / pristine / upstream-landed / drifted
128
+ by hash, exit 1 on drift with the exact rebuild commands.
129
+
130
+ ### Fixed
131
+
132
+ - **The input chip now refreshes on change events.** Change events identify the
133
+ *workspace*, and the client matched them against its own target id. A sidebar row
134
+ targets by workspace id so rows kept working, but the chip targets by session id,
135
+ which can never equal a workspace id — so its handler never fired and the chip
136
+ only updated when it remounted (switching conversations) or on the 60s fallback
137
+ poll. The status response now echoes the resolved workspace id and the client
138
+ matches on that. This was a regression in the server-side resolution change.
139
+ - **A linked worktree's badge only refreshed on a file edit.** The watcher watched
140
+ the workspace root, but a linked worktree's git dir lives OUTSIDE it
141
+ (`<main>/.git/worktrees/<name>`), so `git add`, `git commit` and `git checkout`
142
+ there wrote nothing the root watch could see: the badge stayed stale until a
143
+ worktree file changed or the client's 60s poll fired. The per-worktree git dir
144
+ now gets its own watcher, resolved from the `.git` gitfile (which covers
145
+ submodules too), and either watcher erroring drops both and hands over to the
146
+ degraded poll. A regression test that stages a file written before the watch
147
+ existed fails without the second watcher.
148
+ - **The TTL fetch no longer delays the status response.** `ahead`/`behind` are
149
+ read from the local remote-tracking ref, and the fetch that refreshes it was
150
+ awaited before answering, so any request arriving after the 60s TTL had lapsed
151
+ paid a full network fetch first (~3.3s measured) — which is the first request
152
+ after every TTL window, i.e. constantly while editing. Badge updates triggered
153
+ by an edit took seconds for a repo with a remote. The response is now served
154
+ from the refs at hand and the fetch runs out of band; when it succeeds it
155
+ notifies subscribers, which reconverge in ~40ms. Same call, expired TTL: 27ms.
156
+ The trade is that `ahead`/`behind` can lag by up to one fetch — branch, dirty
157
+ state and file counts are never delayed.
158
+ - The plugin's `surfaces:` console line reported the boot-time race instead of the
159
+ outcome: it printed `sidebar rows = off (seam absent)` while row badges were
160
+ visibly rendering, because it sampled the seam declaration before the workspace
161
+ browser published it. It now reports from the registration callback, and the
162
+ boot-time line says it is awaiting the seam rather than declaring it missing.
163
+
164
+ ## [0.6.0] - 2026-09-12
165
+
166
+ ### Fixed
167
+
168
+ - **`✎n` under-counted new files.** Git collapses a new directory into a single
169
+ status entry, so adding a folder of 3 files displayed `✎1`. Untracked files are
170
+ now counted individually. If walking a very large untracked tree exceeds the time
171
+ budget, the badge falls back to the collapsed count and reports which mode
172
+ answered (`untrackedMode`) instead of silently showing a wrong number.
173
+
174
+ ### Added
175
+
176
+ - **An in-progress operation token.** A paused merge, rebase, cherry-pick, revert,
177
+ bisect or sequencer run now appears after the branch name —
178
+ `🟡 main ⚔rebase ↑0 ↓2 ✎3`. A rebase whose conflicts are already staged has no
179
+ unmerged files, so the three-state dot could not express it. The dot contract
180
+ itself is unchanged, and the token is chip-only.
181
+ - **A 41-test node-half suite** (`npm test`) covering the status parser, git
182
+ plumbing against real repositories, request routing, SSE framing and the
183
+ filesystem watcher — runnable without booting DSH.
184
+ - **A CI test workflow** running the suite on Node 20/22/24.
185
+
186
+ ### Changed
187
+
188
+ - Publishing runs through **npm trusted publishing** (OIDC) on a published
189
+ release, gated on the suite, with SLSA provenance — no long-lived token.
190
+ - Event stream hardening: `x-accel-buffering: no`, named `change` frames, cleanup
191
+ on disconnect, and an unload disposer that also releases the filesystem watchers.
192
+ - `detail=1` is annotated as having no consumer yet (the hover card remains
193
+ pending). Its comment previously claimed `log -1` while the code ran `log -3`.
194
+
195
+ [Unreleased]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/compare/v0.8.0...HEAD
196
+ [0.8.0]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/releases/tag/v0.8.0
197
+ [0.7.0]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/releases/tag/v0.7.0
198
+ [0.6.0]: https://github.com/Kevin-McIsaac/dsh-workspace-git-badge/releases/tag/v0.6.0
package/README.md CHANGED
@@ -2,10 +2,14 @@
2
2
 
3
3
  Git status badges for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness):
4
4
 
5
- - **Input-row chip** — `🟢 main` next to the access picker, showing the git state of the workspace the current conversation is attached to. Works on every install.
6
- - **Sidebar row badges** — full Claude-Code-statusline-style badges (`| 🟡 main ↑0 ↓2 ✎3`) on workspace rows. Requires the `sidebar.workspaces.row` seam; without it the plugin degrades to chip-only and logs what's active to the browser console.
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 three commits, and the stash. Fetched only on hover, so the everyday badge pays nothing for it. Works on every install.
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
+ - **Sidebar row badges** — the same status mark floated to the right of each workspace row (`Project 1 ●`), with a linked worktree's name beside it (`Project 2 hotfix-tree 🌳`). Requires the `sidebar.workspaces.row` seam; without it the plugin degrades to chip-only and logs what's active to the browser console. The hover card and PR token are chip-only, so they never need the seam.
7
9
 
8
- Event-driven freshness: working trees are watched and updates push over SSE — badges flip within ~1s of any commit, stage, or edit. One `git status --porcelain=v2` call per sample; the API only answers registered workspace paths.
10
+ The leading **mark** is identical on both surfaces: a circle filled with the status colour, or a **tree** when the workspace is a linked `git worktree`. (`●`/`🌳` stand in for it here — it is a drawn SVG so that it can take the theme's status colours.)
11
+
12
+ Event-driven freshness: working trees are watched and updates push over SSE — badges flip within ~1s of any commit, stage, or edit. One `git status --porcelain=v2` call per sample (plus optionally one `gh` call per ~90s, chip only). The status API takes **no path**: the caller passes a session id (chip) or workspace id (row) and the server resolves the directory itself.
9
13
 
10
14
  ## Install
11
15
 
@@ -13,10 +17,14 @@ Event-driven freshness: working trees are watched and updates push over SSE —
13
17
  dsh plugin --profile web add dsh-git-badge
14
18
  ```
15
19
 
16
- Restart the web process, refresh the browser. Requires Node ≥ 20 and `git` on PATH.
20
+ Restart the web process, refresh the browser. Requires Node ≥ 20 and `git` on PATH. `gh`, on PATH and authenticated, is optional — it only enables the PR/CI token.
17
21
 
18
22
  Seam details: [github.com/Kevin-McIsaac/dsh-workspace-git-badge](https://github.com/Kevin-McIsaac/dsh-workspace-git-badge) (SEAM.md / PR.md).
19
23
 
24
+ ## Changelog
25
+
26
+ See [CHANGELOG.md](CHANGELOG.md).
27
+
20
28
  ## License
21
29
 
22
30
  MIT