git-reviewer 2.7.0 → 2.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/README.md +2 -2
- package/package.json +1 -1
- package/public/app.js +108 -14
- package/public/index.html +2 -1
- package/public/style.css +5 -0
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://github.com/dheerajjha/reviewer/actions/workflows/ci.yml)
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
[](package.json)
|
|
6
|
-
[](test/)
|
|
7
7
|
[](package.json)
|
|
8
8
|
|
|
9
9
|
When an agent writes the code, reading it becomes the bottleneck — and the tool
|
|
@@ -294,7 +294,7 @@ rather than a public issue.
|
|
|
294
294
|
|
|
295
295
|
```bash
|
|
296
296
|
npm install # 3 dependencies, no build step, ~6MB
|
|
297
|
-
npm test #
|
|
297
|
+
npm test # 284 tests
|
|
298
298
|
npm run test:watch
|
|
299
299
|
npm run test:coverage
|
|
300
300
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "git-reviewer",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
4
|
"description": "Review your coding agent's changes like a pull request — locally, with inline comments and threaded follow-ups, then hand the marked-up review back to the agent",
|
|
5
5
|
"author": "Dheeraj Jha",
|
|
6
6
|
"license": "MIT",
|
package/public/app.js
CHANGED
|
@@ -426,7 +426,7 @@ function displayCode(filePath, diffLines, binary) {
|
|
|
426
426
|
const newLineClick = diffLine.type !== 'delete' ? `onclick="toggleCommentInput(${Number(diffLineIndex)})"` : '';
|
|
427
427
|
|
|
428
428
|
html += `
|
|
429
|
-
<div class="${lineClass}" data-diff-index="${diffLineIndex}">
|
|
429
|
+
<div class="${lineClass}" tabindex="0" data-diff-index="${diffLineIndex}">
|
|
430
430
|
${hasComment ? '<span class="comment-indicator"></span>' : ''}
|
|
431
431
|
<div class="line-numbers">
|
|
432
432
|
<span class="old-line-number" ${oldLineClick}>${diffLine.oldLine || ''}</span>
|
|
@@ -506,7 +506,7 @@ function displayCode(filePath, diffLines, binary) {
|
|
|
506
506
|
// rendered from. The row's random id went with it: the position is the
|
|
507
507
|
// identifier now.
|
|
508
508
|
html += `
|
|
509
|
-
<div class="unmatched-comment-item collapsed">
|
|
509
|
+
<div class="unmatched-comment-item collapsed" tabindex="0">
|
|
510
510
|
<div class="unmatched-comment-header" onclick="toggleUnmatchedComment(${Number(unmatchedIndex)})">
|
|
511
511
|
<span class="unmatched-comment-icon">⚠️</span>
|
|
512
512
|
<span class="unmatched-comment-title">Comment not found (Line ${comment.line} changed)</span>
|
|
@@ -1357,7 +1357,7 @@ function displayFullContext(filePath, lines) {
|
|
|
1357
1357
|
const dataDiffIndexAttr = diffLineIndex !== undefined ? `data-diff-index="${diffLineIndex}"` : '';
|
|
1358
1358
|
|
|
1359
1359
|
html += `
|
|
1360
|
-
<div class="${lineClass}" ${dataDiffIndexAttr}>
|
|
1360
|
+
<div class="${lineClass}" tabindex="0" ${dataDiffIndexAttr}>
|
|
1361
1361
|
${hasComment ? '<span class="comment-indicator"></span>' : ''}
|
|
1362
1362
|
<div class="line-numbers">
|
|
1363
1363
|
<span class="old-line-number">${diffLine.oldLine || ''}</span>
|
|
@@ -1636,6 +1636,76 @@ function navigateFiles(direction) {
|
|
|
1636
1636
|
}
|
|
1637
1637
|
}
|
|
1638
1638
|
|
|
1639
|
+
// Navigate to comments in current file
|
|
1640
|
+
function navigateComments(direction) {
|
|
1641
|
+
const commentElements = Array.from(document.querySelectorAll('.line.commented, .unmatched-comment-item'));
|
|
1642
|
+
if (commentElements.length === 0) return;
|
|
1643
|
+
|
|
1644
|
+
const active = document.activeElement;
|
|
1645
|
+
const currentIndex = commentElements.findIndex(el => el === active || el.contains(active));
|
|
1646
|
+
|
|
1647
|
+
let newIndex = currentIndex;
|
|
1648
|
+
if (currentIndex === -1) {
|
|
1649
|
+
newIndex = direction === 'next' ? 0 : commentElements.length - 1;
|
|
1650
|
+
} else {
|
|
1651
|
+
// stop at boundaries, matching existing file navigation style
|
|
1652
|
+
if (direction === 'next') {
|
|
1653
|
+
newIndex = Math.min(currentIndex + 1, commentElements.length - 1);
|
|
1654
|
+
} else {
|
|
1655
|
+
newIndex = Math.max(currentIndex - 1, 0);
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
if (newIndex !== currentIndex && commentElements[newIndex]) {
|
|
1660
|
+
const target = commentElements[newIndex];
|
|
1661
|
+
target.focus();
|
|
1662
|
+
target.scrollIntoView({ block: 'nearest' });
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
function handleCommentShortcut() {
|
|
1667
|
+
const active = document.activeElement;
|
|
1668
|
+
if (!active) return;
|
|
1669
|
+
const line = active.closest('.line');
|
|
1670
|
+
if (!line) return;
|
|
1671
|
+
const diffIndex = parseInt(line.dataset.diffIndex, 10);
|
|
1672
|
+
if (!isNaN(diffIndex)) {
|
|
1673
|
+
toggleCommentInput(diffIndex);
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
/**
|
|
1678
|
+
* Pure function mapping keyboard events to application actions.
|
|
1679
|
+
* @param {string} key
|
|
1680
|
+
* @param {string} targetTagName
|
|
1681
|
+
* @param {{ctrl?: boolean, meta?: boolean, alt?: boolean}} [modifiers]
|
|
1682
|
+
* @returns {string|null} Action to take, or null if ignored.
|
|
1683
|
+
*/
|
|
1684
|
+
function getKeyboardShortcut(key, targetTagName, modifiers = {}) {
|
|
1685
|
+
if (modifiers.ctrl || modifiers.meta || modifiers.alt) return null;
|
|
1686
|
+
|
|
1687
|
+
// Escape means "never mind" everywhere — including inside inputs — so it
|
|
1688
|
+
// must be checked before the typing guard.
|
|
1689
|
+
if (key === 'Escape') return 'escape';
|
|
1690
|
+
|
|
1691
|
+
if (targetTagName === 'INPUT' || targetTagName === 'TEXTAREA') {
|
|
1692
|
+
return null;
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
const map = {
|
|
1696
|
+
'ArrowUp': 'prevFile',
|
|
1697
|
+
'k': 'prevFile',
|
|
1698
|
+
'ArrowDown': 'nextFile',
|
|
1699
|
+
'j': 'nextFile',
|
|
1700
|
+
'n': 'nextComment',
|
|
1701
|
+
'p': 'prevComment',
|
|
1702
|
+
'c': 'commentFocus',
|
|
1703
|
+
'?': 'help'
|
|
1704
|
+
};
|
|
1705
|
+
|
|
1706
|
+
return map[key] || null;
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1639
1709
|
// Allow Enter key to load repo
|
|
1640
1710
|
document.addEventListener('DOMContentLoaded', () => {
|
|
1641
1711
|
document.getElementById('repoPath').addEventListener('keypress', (e) => {
|
|
@@ -1647,22 +1717,31 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1647
1717
|
// Initialize resize handle
|
|
1648
1718
|
initResizeHandle();
|
|
1649
1719
|
|
|
1650
|
-
// Add keyboard navigation
|
|
1720
|
+
// Add keyboard navigation
|
|
1651
1721
|
document.addEventListener('keydown', (e) => {
|
|
1652
|
-
|
|
1653
|
-
if (
|
|
1654
|
-
return;
|
|
1655
|
-
}
|
|
1722
|
+
const action = getKeyboardShortcut(e.key, e.target.tagName, { ctrl: e.ctrlKey, meta: e.metaKey, alt: e.altKey });
|
|
1723
|
+
if (!action || action === 'escape') return; // escape is handled separately
|
|
1656
1724
|
|
|
1657
|
-
if (
|
|
1725
|
+
if (action === 'prevFile') {
|
|
1658
1726
|
e.preventDefault();
|
|
1659
1727
|
navigateFiles('up');
|
|
1660
|
-
} else if (
|
|
1728
|
+
} else if (action === 'nextFile') {
|
|
1661
1729
|
e.preventDefault();
|
|
1662
1730
|
navigateFiles('down');
|
|
1731
|
+
} else if (action === 'nextComment') {
|
|
1732
|
+
e.preventDefault();
|
|
1733
|
+
navigateComments('next');
|
|
1734
|
+
} else if (action === 'prevComment') {
|
|
1735
|
+
e.preventDefault();
|
|
1736
|
+
navigateComments('prev');
|
|
1737
|
+
} else if (action === 'commentFocus') {
|
|
1738
|
+
e.preventDefault();
|
|
1739
|
+
handleCommentShortcut();
|
|
1740
|
+
} else if (action === 'help') {
|
|
1741
|
+
e.preventDefault();
|
|
1742
|
+
document.getElementById('helpButton').click();
|
|
1663
1743
|
}
|
|
1664
1744
|
});
|
|
1665
|
-
|
|
1666
1745
|
for (const id of ['recentList', 'browseList']) {
|
|
1667
1746
|
const list = document.getElementById(id);
|
|
1668
1747
|
list.addEventListener('click', onPickerActivate);
|
|
@@ -1672,13 +1751,22 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1672
1751
|
// Escape, and a click on the dimmed area around it, both mean "never mind"
|
|
1673
1752
|
// -- but only when there is a review behind the picker to go back to.
|
|
1674
1753
|
document.addEventListener('keydown', event => {
|
|
1675
|
-
if (event.key !== '
|
|
1754
|
+
if (getKeyboardShortcut(event.key, event.target.tagName, { ctrl: event.ctrlKey, meta: event.metaKey, alt: event.altKey }) !== 'escape') {
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1676
1757
|
|
|
1677
1758
|
// Topmost first: the help sits over the picker, which sits over a review.
|
|
1678
1759
|
if (!document.getElementById('helpModal').classList.contains('hidden')) {
|
|
1679
1760
|
closeHelp();
|
|
1680
1761
|
return;
|
|
1681
1762
|
}
|
|
1763
|
+
|
|
1764
|
+
const reviewModal = document.querySelector('.review-modal');
|
|
1765
|
+
if (reviewModal) {
|
|
1766
|
+
reviewModal.remove();
|
|
1767
|
+
return;
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1682
1770
|
closePicker();
|
|
1683
1771
|
});
|
|
1684
1772
|
|
|
@@ -1817,8 +1905,8 @@ async function loadRecent() {
|
|
|
1817
1905
|
.map(project => {
|
|
1818
1906
|
const meta = project.exists
|
|
1819
1907
|
? [project.comments > 0 ? `${project.comments} comment${project.comments === 1 ? '' : 's'}` : '', timeAgo(project.openedAt)]
|
|
1820
|
-
|
|
1821
|
-
|
|
1908
|
+
.filter(Boolean)
|
|
1909
|
+
.join(' · ')
|
|
1822
1910
|
: 'no longer there';
|
|
1823
1911
|
|
|
1824
1912
|
return `
|
|
@@ -1972,3 +2060,9 @@ function offerHelpOnce() {
|
|
|
1972
2060
|
if (helpWasSeen()) return;
|
|
1973
2061
|
openHelp();
|
|
1974
2062
|
}
|
|
2063
|
+
|
|
2064
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
2065
|
+
module.exports = {
|
|
2066
|
+
getKeyboardShortcut
|
|
2067
|
+
};
|
|
2068
|
+
}
|
package/public/index.html
CHANGED
|
@@ -149,7 +149,8 @@
|
|
|
149
149
|
<div class="help-foot">
|
|
150
150
|
<span class="help-keys">
|
|
151
151
|
<kbd>⌘</kbd><kbd>Enter</kbd> save · <kbd>Esc</kbd> cancel ·
|
|
152
|
-
<kbd>↑</kbd><kbd>↓</kbd>
|
|
152
|
+
<kbd>↑</kbd><kbd>↓</kbd><kbd>j</kbd><kbd>k</kbd> files ·
|
|
153
|
+
<kbd>n</kbd><kbd>p</kbd> comments · <kbd>c</kbd> new comment · <kbd>?</kbd> help
|
|
153
154
|
</span>
|
|
154
155
|
<button class="help-done" onclick="closeHelp()">Got it</button>
|
|
155
156
|
</div>
|
package/public/style.css
CHANGED
|
@@ -441,6 +441,11 @@ button:disabled {
|
|
|
441
441
|
background: rgba(0, 0, 0, 0.03);
|
|
442
442
|
}
|
|
443
443
|
|
|
444
|
+
.line:focus, .unmatched-comment-item:focus {
|
|
445
|
+
outline: 2px solid #0366d6;
|
|
446
|
+
outline-offset: -2px;
|
|
447
|
+
}
|
|
448
|
+
|
|
444
449
|
/* Diff-specific styling */
|
|
445
450
|
.diff-add {
|
|
446
451
|
background: #e6ffed;
|