Skip to content

feat: add diff hunk/block hover affordance and hunk/line discard - #26

Open
ashproto wants to merge 27 commits into
nextfrom
feat/diff-hunk-affordance
Open

feat: add diff hunk/block hover affordance and hunk/line discard#26
ashproto wants to merge 27 commits into
nextfrom
feat/diff-hunk-affordance

Conversation

@ashproto

@ashproto ashproto commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Adds a Fork/SourceTree-style hover affordance to the working-copy diff, adds hunk- and line-level Discard (which the app had at no granularity below whole-file), and fixes the line-selection visual that was effectively invisible in the NERV theme.

The three problems this solves

1. Selection was invisible in NERV. .diff-row.selected was box-shadow: inset 2px 0 0 var(--accent) plus a 15% accent wash. In Classic that's blue over near-white and reads fine; in NERV the accent is orange #F2542D and a deleted row's background is already rgba(255,68,56,0.15), so 15% orange over 15% red on a #12171C panel was close to no change at all — and nerv.css recolors --accent per scheme, so crimson and phosphor made it worse. Fixed by deleting the rule: selection now reuses the hover ring. That works because hover is fully suppressed while a selection is locked, so the two can never both be on screen and can safely share one visual.

2. No way to discard a hunk or a line range. Added end to end. git_apply gains a cached flag; dropping --cached turns the existing reverse-apply into a worktree discard. Because the unstaged diff's old side is the index, discarding reverts to your staged state rather than HEAD — staged work on the same file survives, which is what makes Discard safe to sit beside Stage.

3. No hover affordance. Nested rings — hunk outline (a CSS outline on a per-hunk <tbody>) and an inner block ring — with a floating Stage/Discard/Unstage toolbar. Double-click locks a contiguous selection, Shift+click extends, plain click clears. Unified and Split.

Two bugs found during review, both fixed here

The toolbar was unclickable. onmouseleave sat on the <tbody> while the toolbar lives outside the table, so moving the pointer toward it unmounted it before the click landed (b4abb4c).

Split-view selection silently reordered files. A "paired row" is a display artifacttoSplitRows puts the Nth deletion beside the Nth addition because they fit on a line together, not because they correspond. Selecting one pair emitted non-contiguous ordinals ([2,5]), and build_partial_hunk emits in hunk source order, so the restored line landed above the lines the user kept. Reproduced against real git: discarding a pair produced ccc XXX YYY instead of XXX YYY ccc, accepted silently, with no reflog to recover from.

Fixed in two layers: split selection now snaps to whole blocks (e11b9ef), and git-core refuses a gapped ordinal set outright (ad86a8d) so no future caller can reintroduce it. The invariant: only contiguous ordinal sets are correct — unified ranges and whole blocks always are; partial coverage of a mixed block never is.

Also included

  • Discard/Stage were unreachable for partially-staged files. selectedIsStaged meant "has any staged content", so an MM file always showed its staged diff even when you clicked its Unstaged row — hiding hunk/line Stage and Discard on the very half those actions exist for. Selection now records which list the row was clicked in (41ec3b5, 0981c36).
  • Whole-file mode made "Discard hunk" revert every unstaged change in a file behind a dialog saying "this hunk"; it now states the real changed-line count.
  • Roving tabindex — the branch had given every diff row a tab stop, injecting hundreds into the global tab order.
  • Hunk actions pin to the hunk's @@ header bar; block/selection actions anchor to the ring, so the toolbar's position tells you what it will hit.

Testing

npm run check 530 files / 0 errors, 0 warnings · npm test 386 passed (343 baseline + 43 new) · cargo test -p git-core 190 passed

New pure modules with vitest coverage: src/lib/diff/blocks.ts, src/lib/diff/splitRows.ts, src/lib/workingSection.ts. Two coverage holes were closed by mutation testing — both isChangeRow clauses could previously be deleted with the whole suite staying green.

Reviewed per-task throughout, plus a 22-agent whole-branch review across five dimensions with adversarial refutation of every finding.

Manually verified in a running build (28 items), including the four that no automated test can reach: the <tbody> outline does render in WKWebView; the ring is legible in all six NERV schemes plus Classic; discard reverts the correct lines with staged work surviving (checked against git show :file); and a whole-block split discard leaves the file identical to HEAD — no reordering.

Known / not verified

  • Escape-to-clear could not be verified. Synthetic Escape never reached the WebView (three independent Escape-sensitive surfaces were all unresponsive while Tab worked), so this is untested rather than known-broken. Worth one manual press.
  • Keyboard navigation is largely unverified for the same reason.
  • The hunk-level outline is faint (color-mix(--diff-ring 34%) at 1px) and is the only indicator of hunk scope for a destructive action — may deserve strengthening.
  • --diff-ring exists as a token so nerv.css can retune a scheme without touching component markup.

🤖 Generated with Claude Code

ashproto and others added 21 commits August 6, 2026 18:05
Design for a nested hover affordance (hunk + change-block rings with
floating stage/discard actions) in the working-copy diff, hunk- and
line-level discard, and a replacement for the line-selection visual
that is effectively invisible in the NERV theme.

Interaction model validated against a live prototype before writing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Eight TDD tasks: pure block/ordinal helpers, git-core discard ops,
Tauri/api/gitActions plumbing, hover rings, floating toolbar, range
selection, split view, and keyboard parity.

Also corrects the spec's ring mechanism: box-shadow does not merge
across rules but custom properties do, so the block ring is pure CSS
rather than a JS-positioned overlay. Only the toolbar needs measuring.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Shift+Arrow steps by BLOCK in split view, where row-stepping was a no-op after
  selection snapping (new `splitBlockRanges`, which `splitRangeSnappedToBlocks` now reuses).
- The hunk-scope toolbar anchors to the hovered row, so hunks taller than the pane no
  longer hide it.
- The Discard confirm states the changed-line count instead of "this hunk", which
  understated Whole-file mode's single whole-file hunk.
- Roving tabindex: one tab stop per diff instead of one per row, with unshifted arrows
  moving between rows and Tab reaching the toolbar.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Implementation disproved the spec's claim that a paired split row could
select "as a unit". A paired row is a display artifact, so selecting one
emits non-contiguous ordinals and build_partial_hunk — which emits in
hunk source order — silently reorders the file. Verified against real git.

Selection now snaps to whole change blocks, enforced in the frontend and
refused outright by git-core as a second line of defence.

Also folds in two plan corrections made during execution: hover must clear
on the outer wrapper rather than the tbody, and the min-height/min-width
discrepancy in Task 5.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
selectedIsStaged asked "does this file have any staged content", so a
partially-staged (MM) file always showed its STAGED diff even when the user
clicked its Unstaged row. Every action gated on that flag was therefore
unavailable on the unstaged half: not just the new hunk/line Discard, but
hunk/line Stage too — both sides of the workflow the feature exists for.

Selection now records the section the row was clicked in, the same
discriminator onRowContext already used for its menu. resolveSection() picks
the section to display, falling back to wherever the file went if it has left
the clicked one (staging all of it, say) rather than stranding an empty diff.
Row highlight follows the resolved section, so an MM file highlights only the
row being diffed instead of both.

Discarding unstaged lines of a partially-staged file reverts them to the
staged version rather than to HEAD, so the confirm dialog now says that
instead of claiming the change is permanently lost.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
presence.unstaged checked unstagedFiles, which excludes untracked, while the
rendered Unstaged list is unstagedDisplay, which includes them when "Merge
Untracked into Unstaged" is on. A merged untracked row therefore reported
section "unstaged" but resolved to "untracked" — a section with no rows in
that mode — so the selected row never highlighted, clicking it again could
not deselect, and the Unstaged header fell back to "Stage all".

Sections are rendered lists, not file states, so presence now describes what
is on screen. Two comments that asserted the opposite are corrected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Hunk-scope actions anchored to hov.ri, so every context row the pointer
crossed re-anchored the toolbar and it chased the cursor down the hunk.
That anchor existed to stop the toolbar hiding on hunks taller than the
pane; clamping (d8b84c6) now covers that, so the anchor can be stable.

Hunk actions now sit on the hunk's own @@ header bar, where their scope is
written. Block and selection actions still anchor to their ring, so the
toolbar's position says which of the two you are targeting.

The header row gains data-h/data-i as the anchor, which also made it match
the arrow-key row query; that selector is narrowed to .diff-row so ArrowDown
cannot land on a non-focusable header and dead-end.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review

let d = diff(repo, Some(path), false, context)?;
let (header, hunks) = split_hunks(&d);
let h = hunks.get(hunk_index).ok_or("hunk index out of range")?;

P1 Badge Validate the selected patch before destructive apply

If the file changes in an external editor while the discard confirmation is open, an inserted or removed earlier hunk can reindex the diff before this command runs. The caller still submits the old numeric index, but discard_hunk fetches the new live diff and blindly reverse-applies whichever hunk now occupies that slot, permanently discarding unrelated work; discard_lines has the same issue for both hunk and line ordinals. Pass and verify an expected hunk identity or patch fingerprint before applying.


let partial = build_partial_hunk(h, &set, true).ok_or("no lines selected to discard")?;

P2 Badge Preserve both hunk coordinates for partial discards

When the user reduces diff context to 0 and discards lines from a later hunk after an earlier hunk has changed the line count, this calls build_partial_hunk, which reconstructs both sides using only the original old-side start. For a live header such as @@ -8,0 +10 @@, it emits @@ -8,0 +8,1 @@; git apply --reverse then reports that the patch does not apply, so line discard fails for that hunk. Parse and retain the original new-side start as well.


const suppressNativeContextMenu = (event: MouseEvent) => event.preventDefault();
window.addEventListener("focus", onActivate);
document.addEventListener("visibilitychange", onActivate);
window.addEventListener("contextmenu", suppressNativeContextMenu);

P2 Badge Keep context menus available in editable content

This window-level handler prevents every native context menu, including those on commit-message inputs, review textareas, diffs, and rendered Markdown, while the repository only supplies custom menus for a few row types. Consequently, users who right-click editable or selectable content lose mouse access to copy, paste, spelling suggestions, and related platform actions. Limit suppression to elements that provide a custom menu or to non-editable chrome.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…tale diff

Both from the Codex review on #26. Investigating the first turned up a worse
bug underneath it.

## Hunk placement at -U0

`git apply` positions a hunk by the coordinate of the image it produces —
new_start forward, old_start under --reverse — then offset-searches for the
preimage around it. That search is why a wrong coordinate normally goes
unnoticed, and it is exactly what hid this: at context >= 1 every case still
landed correctly.

At -U0 a pure insertion (forward) or a pure deletion (reverse) has an EMPTY
preimage. Nothing to search for, so git applies at precisely the line named
and reports success from the wrong place. Two defects met there:

- A hunk lifted out of a multi-hunk diff keeps BOTH coordinates from the full
  diff, so the side not facing the apply target is offset by the hunks left
  behind. `build_partial_hunk` made it worse by writing old_start into both
  slots. New `reanchor` keeps the trustworthy side and derives the other; it
  is now shared with the whole-hunk ops, which had the same fault by way of
  replaying git's live header.
- Without --unidiff-zero git enforces "a hunk with no trailing context must
  match at EOF", which every context-free hunk trips. Passed only when
  context == 0; at any real depth those checks stay on.

Removing either half alone reintroduces failures (13 and 5 respectively), so
both are load-bearing. Worst case found: at -U0, staging a hunk silently
appended the line at end-of-file and returned Ok. Restoring a deleted line via
discard was mis-placed the same way — on the path with no reflog.

## Stale diff under the confirmation dialog

A hunk index is just an integer; nothing in it says which hunk it meant. The
discard confirmation has no timeout, and the fswatch refresh never invalidates
the captured index, so an external edit could re-split the file and the op
would reverse-apply a hunk the user never saw.

The displayed diff now travels with the request and git-core compares it to the
live one BEFORE selecting any hunk, so a mismatch cannot touch the working
tree. Whole-file rather than per-hunk: distinguishing a harmless edit would
mean trusting the same index arithmetic that is in question.

Left alone deliberately: stage_hunk/unstage_hunk freshness (same window, but
they mutate the index, which is recoverable).

27 tests over four shapes x six ops. cargo test -p git-core 220 passed;
npm run check 530 files 0 errors; npm test 386 passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ashproto

ashproto commented Aug 8, 2026

Copy link
Copy Markdown
Owner Author

Addressed in eccbee2. Two of the three findings were real; one is out of scope.

P1 — validate the selected patch before destructive apply ✅ fixed

Confirmed, and the exposure window is wider than described: it is the entire confirmation dialog, which has no timeout. runAction captures the index before the await dialogs.confirm(...) resolves, and the fswatch-driven refresh never invalidates it — it only clears DiffView's local selection state, so confirming still submits the originally captured index.

The displayed diff now travels with the request; git-core compares it to the live one before split_hunks, so a mismatch cannot reach the working tree. Whole-file rather than per-hunk deliberately: telling a harmless edit apart would mean trusting the same index arithmetic that is in question.

Left alone on purpose: stage_hunk/unstage_hunk have the same window but mutate the index, which is recoverable.

P2 — preserve both hunk coordinates ✅ fixed, though not for the stated reason

The header defect is real, but the mechanism in the report does not hold up. I tested @@ -14,6 +15,7 @@ emitted as @@ -14,6 +14,7 @@, and git apply --reverse succeeds with the wrong coordinate — it offset-searches for the preimage and lands correctly. At context ≥ 1 no case fails. Conversely, at -U0 the patch failed with the correct new_start too, so the header was not what was breaking it.

Chasing that turned up something worse. At -U0 a pure insertion (forward) or pure deletion (reverse) has an empty preimage — no search possible, so git applies exactly where the header says. Two defects met there:

  1. Coordinate: an extracted hunk keeps both coordinates from the full diff, so the side not facing the apply target is offset by the hunks left behind. build_partial_hunk compounded it by writing old_start into both slots. New reanchor keeps the trustworthy side and derives the other, now shared with the whole-hunk ops — stage_hunk had the same fault by replaying git's live header.
  2. Missing --unidiff-zero: git enforces "a hunk with no trailing context must match at EOF", which every context-free hunk trips. Passed only when context == 0; at any real depth those checks stay on.

Ablating either half alone reintroduces failures (13 and 5), so both are load-bearing. Worst case found: at -U0, staging a hunk silently appended the line at end-of-file and returned Ok. Restoring a deleted line via discard was mis-placed the same way — on the path with no reflog.

27 tests across four shapes × six ops. cargo test -p git-core 220 · npm run check 530 files 0 errors · npm test 386.

P2 — keep context menus available in editable content ⏸️ real, but not this PR

Agreed on the substance, but src/routes/+page.svelte:298 came from 15e51c3 (2026-07-17). This PR's only change to that file is the one-line --diff-ring token at line 671. Tracked separately rather than widening the diff.


Separately, a review pass found a pre-existing silent corruption in build_partial_hunk: a partial selection that splits a -/+ pair at a no-newline EOF merges the two lines (base "a\nt" → worktree "a\nT2", discard_lines(sel=[0]) yields "a\ntT2"). It reproduces byte-for-byte against HEAD, so it is not from this branch — also tracked separately.

@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review

if destination.join(".git").exists() {
return Err("That folder is already a Git repository. Open it instead.".to_string());
}

P2 Badge Detect bare repositories before initializing

When the destination is an existing bare repository, it has no .git child and rev-parse --is-inside-work-tree returns false, so this check misses it. After the non-empty-folder confirmation, git init then creates a nested normal repository inside the bare repository and mutates a directory that should have been rejected as already managed by Git. Probe whether the destination itself is any Git repository, including a bare one, rather than relying only on the presence of .git.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ashproto

ashproto commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

Addressed in #29, not here — crates/git-core/src/git_ops.rs is untouched by this PR (git diff origin/next...HEAD -- crates/git-core/src/git_ops.rs is empty), and the guard came from 15e51c3 (2026-07-17). Same call as the context-menu P2, which went to #27.

The finding is correct. Both guards do miss a bare repository:

guard bare repo
destination.join(".git").exists() false — no .git child
rev-parse --is-inside-work-tree false — inside a bare repo

One correction to the characterisation, since "mutates a directory that should have been rejected" reads worse than what happens. Running the exact command with a bare repo as cwd:

$ (cd bare.git && git init --initial-branch=newbranch)
Initialized empty Git repository in …/bare.git/.git/

bare HEAD after:    refs/heads/main    (unchanged)
refs still present: refs/heads/trunk   (unchanged)
core.bare after:    true               (unchanged)

$ diff -rq bare-backup.git bare.git
Only in bare.git: .git

It nests a repository inside the bare one, but the bare repo survives intact — a wrong state, not data loss. Still exactly what the sibling guard exists to prevent, so it is worth fixing.

Fixed by probing the destination with rev-parse --resolve-git-dir, chosen because it answers for the path given and does not walk up — so a plain folder sitting inside a repository stays a valid destination (that remains is_inside_worktree's job, now pinned by a test). The .git check is kept alongside rather than replaced, so nothing refused before is accepted now. Details and the full probe matrix are in #29.

@ashproto

ashproto commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eccbee2c1d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/git-core/src/ops_worktree.rs Outdated
Comment thread src/lib/gitActions.ts Outdated
ashproto and others added 3 commits August 8, 2026 19:37
`chmod +x` plus an edited line is one diff with one file header, and git puts
`old mode`/`new mode` in that header. Discard reverse-applies the header to the
working tree, and `git apply` honours a mode pair — so "Discard 1 line" also
took the executable bit off. Not in the confirmation, not in the line count,
and not undoable.

Verified against real git: reverse-applying the header with the mode pair took
f.sh from 755 to 644 while reverting the text; dropping the two mode lines left
755 and reverted the text just the same.

Codex flagged `discard_lines`. `discard_hunk` reverse-applies the same header
and loses the bit identically, so both destructive ops now build a content-only
header. Stage and unstage keep the mode deliberately: there it belongs to the
same index entry the caller is moving, and the result is recoverable either way.

cargo test -p git-core: 222 passed (the one failure in a full run is the
`git_ops.rs` init-repo flake, fixed on `next` by #25 and merged in next commit).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A discard carries the patch the user was judging so git-core can refuse one
picked against a diff that has since moved. That guard worked, but the retry
did not: `applyWorkingChanges` compares WorkingFile metadata and skips the
update when it matches, so an external edit to an ALREADY-modified file leaves
the status list identical, never bumps `workingChangesRev`, and the shown patch
stays stale. `runWorktree` does not refresh on failure, so every retry sent the
same stale patch and got the same refusal — until the user happened to reselect
the file or change the context depth.

Add `invalidateWorkingDiff()`, which bumps the revision the diff effect keys
on, and call it when a discard fails. Refusal becomes something the next click
can get past instead of a dead end. Deliberately on any discard failure rather
than only the stale one: after a failed destructive op the shown state is
unverified either way, and matching on message text would be brittle.

Success still refreshes exactly once through `runWorktree` — untouched, so the
stage/unstage row animation is unaffected.

npm run check 530 files 0 errors; npm test 386 passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Picks up #25's init-repo fixture fix so this branch's CI stops rolling the
`git_ops::tests::initialize_repository_*` dice.
@ashproto

ashproto commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2a42da328b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/git-core/src/ops_worktree.rs Outdated
ashproto and others added 2 commits August 8, 2026 20:13
… prefixes

`git add -N` makes a path tracked-and-unstaged, so the UI offers line-level
discard on it. Its diff header says `new file mode` / `--- /dev/null`, but a
partial selection keeps the unselected additions as CONTEXT — an old side the
header denies — and git refused the whole apply with "new file X depends on old
contents". Whole-hunk discard worked, so it looked arbitrary.

The deleted-file mirror needs no repair: reverse-apply DROPS unselected `-`
lines rather than demoting them, so the new side stays empty and `+++ /dev/null`
stays true. The repair is therefore conditioned on the emitted hunk having an
old side, which also leaves a selection covering every addition alone so it
still deletes the file the way a whole-hunk discard does.

Adversarial review caught the first attempt being far worse than the bug. It
rewrote the `---` side by replacing the first `b/` ANYWHERE in the `+++` path.
`diff()` did not pin the prefixes, so under `diff.mnemonicPrefix` (prefixes
become `i/`/`w/`) `w/lib/util.js` became `w/lia/util.js` — git read that as a
rename and applied it: the file the user asked to discard one line from was
emptied, a committed file they never touched was rewritten, and it returned
Ok(()). Reproduced against real git.

Two changes, either of which would have prevented it, because this path has no
reflog:

- `diff()` pins `--src-prefix=a/ --dst-prefix=b/`. The output is not merely
  displayed, it is fed back to `git apply`; the user's config governs what they
  read in a terminal, not what this reconstructs. Also closes a pre-existing
  hazard: under `diff.noprefix` a patch loses a component to apply's `-p1` and
  lands on the wrong file, which affects stage/unstage too.
- The rewrite only ever swaps a LEADING `b/`, and when there is no such prefix
  the header is left completely untouched — the shape that shipped before this
  repair, which git refuses. Half-repairing was never verified, so it is not a
  state worth entering.

`discard_lines_is_unaffected_by_diff_prefix_config` covers both configs and
asserts the neighbour file is untouched; confirmed to fail without the pin.

cargo test -p git-core 225 passed; npm run check 530 files 0 errors;
npm test 386 passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`clippy::if_same_then_else` fired on the two arms that both drop a header line:
the mode pair, and `new file mode` when the new-file header is being repaired.
Name the two conditions and test them together. No behaviour change.

Missed locally because the documented gate is `npm run check` + `npm test` +
`cargo test`, while CI also runs `cargo clippy -p git-core -p git-it -D warnings`.

cargo test -p git-core -p git-it green; clippy clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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