Skip to content

feat: upgrade to hunkdiff 0.21.0 - #8

Merged
muzomer merged 12 commits into
mainfrom
feat-upgrade-to-hunkdiff-0.21.0
Sep 3, 2026
Merged

feat: upgrade to hunkdiff 0.21.0#8
muzomer merged 12 commits into
mainfrom
feat-upgrade-to-hunkdiff-0.21.0

Conversation

@muzomer

@muzomer muzomer commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Bumps hunkdiff to 0.21.0 (extension API generation 16) and takes what the new
generation makes possible or newly requires.

  • dim for marked context. Context lines shared the amber of the lines that move, overstating a mark's reach. context_marks = "edge" / "full" now use 0.21's dim tone; the default stays "none".
  • Refuse reviews with no uncommitted work. 0.21 added hunk diff <a> <b>, where D previously passed every text check and edited the working copy anyway. Now asks the VCS before any dialog.
  • L — the marked-set pane. Lists each marked file and its hunk count beside the diff, with the cursor's file picked out. For large reviews, where the scattered marks C will take cannot be read off the diff.
  • Automate hunkdiff upgrade. Create claude routine triggered when there is a hunkdiff upgrade to push commits on Renovate PR to address upgrade changes.

muzomer and others added 12 commits September 3, 2026 18:21
Context lines of a marked hunk borrowed the same amber as the lines that
move, which overstated how much of the file a mark would actually take.
That was not a choice so much as a constraint: the only other tones
available either shifted brightness alone and vanished against an added
line's green, or already meant something else in the diff.

Hunk 0.21 (extension API 16) adds `dim`, which recedes a line toward its
background while preserving token hues. Changed lines keep the amber and
context takes `dim`, so `context_marks = "edge"` and `"full"` now trace a
hunk's reach without competing with what will move. The default stays
"none".

Tones are decided per line in buildMarkHighlights, where a line is already
classified, rather than stamped across the set at the call site.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every command here treats the diff on screen as work that has not landed
yet: staging reads the working copy, and discarding writes to it. Hunk
makes no such promise — `hunk show` reviews a commit, and 0.21 added
`hunk diff <from> <to>` for comparing two revisions.

The text checks did not catch this, because they cannot. "This patch
matches the file on disk" is equally true of uncommitted work and of
committed history the working copy agrees with. So on a clean checkout,
reviewing `HEAD~1 HEAD` and pressing D passed every check and reverted
those lines in the working copy — an edit nobody asked for, which rides
into the next commit if it goes unnoticed.

Ask the VCS instead: one `jj diff --name-only`, or `git diff --name-only
HEAD` plus `ls-files --others` so a review holding only an untracked file
still counts. If no reviewed file is uncommitted, refuse before any
dialog. The check is per review rather than per file — a stale file inside
a working-copy review is what the staleness and agreement checks are for.

The README already listed this refusal and already said working-copy
reviews only; the code kept that promise only when the text happened to
disagree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The declaration and the hunkdiff caret range can drift apart silently: the
host refuses only an extension claiming a generation higher than its own,
so a Renovate bump that leaves `apiVersion` behind loads and runs exactly
as before — until someone reaches for an API the declaration does not
claim. Nothing failed; the number was just wrong.

Verified it fails for the right reason by setting apiVersion back to 8:
`Expected: 16, Received: 8`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
package.json declares extension API generation 16, and the host refuses any
extension declaring higher than its own, so the oldest Hunk that will load
this one is 0.21 — 0.20 ships generation 8. The Requirements line still named
0.20 and v8: correct until the 0.21.0 bump, and stranded by it.
CI cannot vouch for a hunkdiff bump. The package is imported almost entirely
as a type-only import, so only tsc covers it, and only for the four interfaces
this repository names. What drifts silently is the prose around it: the stated
minimum Hunk version, the command table, the demo GIFs. This skill is the
reading and reconciling that has to happen by hand — where the release notes
live, which claims to re-check, and when to hand off to demo-gifs.
The 7-day minimumReleaseAge plus schedule:weekly put the hunkdiff PR 7 to 14
days behind the release, and every one of those days is spent not reading
release notes we could have read on day one. Nothing merges itself here —
automerge stays off and the bump is finished by hand — so the PR serves mainly
as the notification that a release happened.

The comment above the rule was stale in two ways, and now says so: the manifest
test imports HUNK_EXTENSION_API_VERSION as a value, so one hunkdiff module
really is evaluated under bun test, and extension API generations drag our
stated minimum Hunk version along with them.
Fires when Renovate opens the PR rather than on a schedule, so it runs the day
the release lands, and commits on top of Renovate's own bump — one PR then
carries the new version and everything the new version implies.

opened and reopened only, never synchronize: the job pushes to the branch that
triggered it. jj is not installed here, so the jj-backed suites self-skip, and
the prompt tells the agent to defer to the CI job on the same commit rather
than report a green run as full coverage.
Marks were visible only as amber inside the diff. Mark hunks in three
files that are screens apart and there was no way to read the set back:
the confirmation counts it ("3 hunks in 2 files") but cannot name it, so
C was pressed on trust. On a small review the diff shows you everything;
this is for the large agent-authored changeset the tool exists for.

Opened with L — M and m are Hunk's menu bar and hunk headers, and
registering a pane does not show it, so without a key it would be
unreachable. Sized as a fraction of the terminal with an 18-column floor,
below which a path stops being readable.

State stays in the MarkStore. Panes unmount when they are closed, so a
component owning the marks would drop them on every close; the store now
publishes changes and hands back a snapshot whose identity moves only
when a mark does — a fresh map per read is an infinite render, a stale one
never repaints.

The listing and path fitting are plain functions in src/ui/markedSet.ts
with their own tests. What is left in the component is only what needs a
renderer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A long marked list is read against a diff scrolled somewhere inside it,
and the two had nothing in common to look at: the list said what was
marked, the review said where you were, and neither said which row you
were standing on. The current file's row now takes the accent colour on
the selected-hunk background.

No `hunk_viewed` subscription, though that is what the issue proposed.
Hunk passes `selectedFileId` and `selectedHunkIndex` into every pane
render, so the component already holds the cursor and a handler would
have re-fetched what was in hand. The event earns its place for consumers
outside a pane — a command, a log — and there are none here.

Nothing is picked out when the cursor sits in a file with no marks, which
is the usual state while still reading.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@muzomer
muzomer marked this pull request as ready for review September 3, 2026 19:38
@muzomer
muzomer merged commit cccd278 into main Sep 3, 2026
1 check passed
@muzomer
muzomer deleted the feat-upgrade-to-hunkdiff-0.21.0 branch September 3, 2026 19:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant