git-reviewer 2.4.1 → 2.6.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 +12 -8
- package/package.json +1 -1
- package/public/app.js +74 -35
- package/public/index.html +75 -22
- package/public/style.css +409 -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
|
|
@@ -32,16 +32,18 @@ review was saved and keeps serving, because you may have more to read.
|
|
|
32
32
|
Everything it says to you goes to stderr either way, so only the review is
|
|
33
33
|
ever on stdout.
|
|
34
34
|
|
|
35
|
-

|
|
36
36
|
|
|
37
|
-
*
|
|
37
|
+
*One command, start to finish, unedited — [full resolution](docs/demo.mp4). Every
|
|
38
|
+
frame is the real tool: the terminal output is captured from the run you are
|
|
39
|
+
watching, and the code at the end is what the agent actually wrote.*
|
|
38
40
|
|
|
39
41
|
Nothing is installed into the project, there is nothing to sign in to, and
|
|
40
42
|
nothing leaves your machine. If you would rather not install it,
|
|
41
43
|
`npx git-reviewer` works the same way.
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
When the review and the hand-off happen at different times, the two-step form
|
|
46
|
+
still works and is the one to reach for:
|
|
45
47
|
|
|
46
48
|
```bash
|
|
47
49
|
reviewer # read the diff, comment on it
|
|
@@ -162,8 +164,10 @@ shown in full, read back out of `HEAD`:
|
|
|
162
164
|
| `Escape` | Cancel input |
|
|
163
165
|
| `↑` / `↓` | Move between files |
|
|
164
166
|
|
|
165
|
-
None of that is guessable
|
|
166
|
-
|
|
167
|
+
None of that is guessable — a column of line numbers does not look clickable —
|
|
168
|
+
so the app shows you rather than telling you. **Reviewing, in four moves** opens
|
|
169
|
+
by itself the first time you review anything, and lives behind the `?` in the
|
|
170
|
+
header after that. Each move is a small animation of the gesture itself.
|
|
167
171
|
|
|
168
172
|
## What it writes
|
|
169
173
|
|
|
@@ -290,7 +294,7 @@ rather than a public issue.
|
|
|
290
294
|
|
|
291
295
|
```bash
|
|
292
296
|
npm install # 3 dependencies, no build step, ~6MB
|
|
293
|
-
npm test #
|
|
297
|
+
npm test # 272 tests
|
|
294
298
|
npm run test:watch
|
|
295
299
|
npm run test:coverage
|
|
296
300
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "git-reviewer",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.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
|
@@ -210,6 +210,7 @@ async function loadRepo() {
|
|
|
210
210
|
const opening = previous === -1 ? 0 : previous;
|
|
211
211
|
|
|
212
212
|
loadFile(currentFiles[opening].path, opening);
|
|
213
|
+
offerHelpOnce();
|
|
213
214
|
}
|
|
214
215
|
|
|
215
216
|
} catch (error) {
|
|
@@ -253,8 +254,12 @@ function displayFiles(files) {
|
|
|
253
254
|
// before the handler is parsed, so an escaped quote is a quote again by
|
|
254
255
|
// the time it matters. The row carries its position instead, and the name
|
|
255
256
|
// is read back out of `currentFiles`, where it stays data.
|
|
257
|
+
// The count is filled in by `updateFileCommentCounts`, not here. Rendering
|
|
258
|
+
// it now would mean re-rendering this list on every comment to keep it
|
|
259
|
+
// true, and re-rendering the list is what loses the selected row.
|
|
256
260
|
return `<div class="file-item" onclick="openFileAt(${Number(index)})" title="${escapeHtml(file)}">
|
|
257
261
|
${statusBadge}<span class="file-path-text">${displayText}</span>
|
|
262
|
+
<span class="file-comment-count" data-file-index="${Number(index)}" hidden></span>
|
|
258
263
|
</div>`;
|
|
259
264
|
}).join('');
|
|
260
265
|
|
|
@@ -1469,12 +1474,46 @@ function initResizeHandle() {
|
|
|
1469
1474
|
let currentFileIndex = -1;
|
|
1470
1475
|
|
|
1471
1476
|
// Update comments sidebar
|
|
1477
|
+
/**
|
|
1478
|
+
* How many comments sit on each file, on the file rows themselves.
|
|
1479
|
+
*
|
|
1480
|
+
* In a review of any size the question you keep asking is which files you have
|
|
1481
|
+
* already covered, and the only answer on screen was a total in the sidebar
|
|
1482
|
+
* header — so you found out by clicking through every file (#48).
|
|
1483
|
+
*
|
|
1484
|
+
* Updated in place rather than by re-rendering the list. Re-rendering would be
|
|
1485
|
+
* one line, and it would drop the selected row and the scroll position of the
|
|
1486
|
+
* file list every time anyone typed a comment.
|
|
1487
|
+
*/
|
|
1488
|
+
function updateFileCommentCounts() {
|
|
1489
|
+
const badges = document.querySelectorAll('.file-comment-count');
|
|
1490
|
+
if (badges.length === 0) return;
|
|
1491
|
+
|
|
1492
|
+
const perFile = new Map();
|
|
1493
|
+
for (const comment of comments) {
|
|
1494
|
+
perFile.set(comment.file, (perFile.get(comment.file) ?? 0) + 1);
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
for (const badge of badges) {
|
|
1498
|
+
const file = currentFiles[Number(badge.dataset.fileIndex)];
|
|
1499
|
+
const count = file ? perFile.get(file.path) ?? 0 : 0;
|
|
1500
|
+
|
|
1501
|
+
badge.textContent = count;
|
|
1502
|
+
badge.title = `${count} comment${count === 1 ? '' : 's'} on this file`;
|
|
1503
|
+
badge.hidden = count === 0;
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1472
1507
|
function updateCommentsSidebar() {
|
|
1473
1508
|
const sidebar = document.getElementById('commentsSidebar');
|
|
1474
1509
|
const commentsList = document.getElementById('commentsList');
|
|
1475
1510
|
const commentsCount = document.getElementById('commentsCount');
|
|
1476
1511
|
const commentsResizeHandle = document.getElementById('commentsResizeHandle');
|
|
1477
1512
|
|
|
1513
|
+
// Every path that changes a comment already calls this, so the per-file
|
|
1514
|
+
// counts ride along rather than needing ten more call sites of their own.
|
|
1515
|
+
updateFileCommentCounts();
|
|
1516
|
+
|
|
1478
1517
|
commentsCount.textContent = comments.length;
|
|
1479
1518
|
|
|
1480
1519
|
if (comments.length === 0) {
|
|
@@ -1624,8 +1663,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1624
1663
|
}
|
|
1625
1664
|
});
|
|
1626
1665
|
|
|
1627
|
-
restoreHowTo();
|
|
1628
|
-
|
|
1629
1666
|
for (const id of ['recentList', 'browseList']) {
|
|
1630
1667
|
const list = document.getElementById(id);
|
|
1631
1668
|
list.addEventListener('click', onPickerActivate);
|
|
@@ -1635,7 +1672,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1635
1672
|
// Escape, and a click on the dimmed area around it, both mean "never mind"
|
|
1636
1673
|
// -- but only when there is a review behind the picker to go back to.
|
|
1637
1674
|
document.addEventListener('keydown', event => {
|
|
1638
|
-
if (event.key
|
|
1675
|
+
if (event.key !== 'Escape') return;
|
|
1676
|
+
|
|
1677
|
+
// Topmost first: the help sits over the picker, which sits over a review.
|
|
1678
|
+
if (!document.getElementById('helpModal').classList.contains('hidden')) {
|
|
1679
|
+
closeHelp();
|
|
1680
|
+
return;
|
|
1681
|
+
}
|
|
1682
|
+
closePicker();
|
|
1639
1683
|
});
|
|
1640
1684
|
|
|
1641
1685
|
document.getElementById('picker').addEventListener('click', event => {
|
|
@@ -1883,53 +1927,48 @@ function onPickerActivate(event) {
|
|
|
1883
1927
|
if (into) loadBrowse(into.dataset.into);
|
|
1884
1928
|
}
|
|
1885
1929
|
|
|
1886
|
-
|
|
1887
|
-
/* How to comment
|
|
1930
|
+
/* How to review
|
|
1888
1931
|
* ---------------------------------------------------------------------------
|
|
1889
|
-
*
|
|
1890
|
-
*
|
|
1932
|
+
* Shown once, on the first review this browser ever opens, and after that only
|
|
1933
|
+
* when asked for. The gestures here are not guessable -- a column of line
|
|
1934
|
+
* numbers does not look clickable -- but they are learned in one go, and a
|
|
1935
|
+
* panel that keeps explaining them afterwards is furniture.
|
|
1891
1936
|
*/
|
|
1892
1937
|
|
|
1893
|
-
/**
|
|
1894
|
-
const
|
|
1938
|
+
/** Set the first time the help has been seen. Per browser; a convenience, not state. */
|
|
1939
|
+
const HELP_SEEN_KEY = 'reviewer.help.seen';
|
|
1895
1940
|
|
|
1896
|
-
/**
|
|
1897
|
-
|
|
1898
|
-
*/
|
|
1899
|
-
function howToWasCollapsed() {
|
|
1941
|
+
/** @returns {boolean} */
|
|
1942
|
+
function helpWasSeen() {
|
|
1900
1943
|
try {
|
|
1901
|
-
return window.localStorage.getItem(
|
|
1944
|
+
return window.localStorage.getItem(HELP_SEEN_KEY) === 'true';
|
|
1902
1945
|
} catch {
|
|
1903
|
-
//
|
|
1904
|
-
//
|
|
1946
|
+
// Private window, or site data blocked. Showing it again is the harmless
|
|
1947
|
+
// direction to fail in.
|
|
1905
1948
|
return false;
|
|
1906
1949
|
}
|
|
1907
1950
|
}
|
|
1908
1951
|
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
*/
|
|
1912
|
-
function rememberHowTo(collapsed) {
|
|
1952
|
+
function openHelp() {
|
|
1953
|
+
document.getElementById('helpModal').classList.remove('hidden');
|
|
1913
1954
|
try {
|
|
1914
|
-
window.localStorage.setItem(
|
|
1955
|
+
window.localStorage.setItem(HELP_SEEN_KEY, 'true');
|
|
1915
1956
|
} catch {
|
|
1916
|
-
// Nothing to do
|
|
1957
|
+
// Nothing to do; it opens again next time.
|
|
1917
1958
|
}
|
|
1918
1959
|
}
|
|
1919
1960
|
|
|
1920
|
-
function
|
|
1921
|
-
|
|
1922
|
-
const collapsed = panel.classList.toggle('collapsed');
|
|
1923
|
-
|
|
1924
|
-
document.getElementById('howToToggle').setAttribute('aria-expanded', String(!collapsed));
|
|
1925
|
-
rememberHowTo(collapsed);
|
|
1961
|
+
function closeHelp() {
|
|
1962
|
+
document.getElementById('helpModal').classList.add('hidden');
|
|
1926
1963
|
}
|
|
1927
1964
|
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1965
|
+
/**
|
|
1966
|
+
* Offer it unprompted the first time, once a repository is actually on screen.
|
|
1967
|
+
*
|
|
1968
|
+
* Not at page load: with nothing open there is nothing to apply it to, and it
|
|
1969
|
+
* would be the first thing between somebody and the tool they just started.
|
|
1970
|
+
*/
|
|
1971
|
+
function offerHelpOnce() {
|
|
1972
|
+
if (helpWasSeen()) return;
|
|
1973
|
+
openHelp();
|
|
1935
1974
|
}
|
package/public/index.html
CHANGED
|
@@ -19,6 +19,13 @@
|
|
|
19
19
|
<span id="repoButtonLabel">Open a repository</span>
|
|
20
20
|
</button>
|
|
21
21
|
|
|
22
|
+
<button id="helpButton" class="icon-button" onclick="openHelp()"
|
|
23
|
+
title="How to review" aria-label="How to review">
|
|
24
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
|
|
25
|
+
<path d="M8 0a8 8 0 100 16A8 8 0 008 0zM6.05 6.04a.75.75 0 01-1.45-.38 3.5 3.5 0 016.86.86c0 1.23-.67 1.93-1.28 2.4l-.13.1c-.61.47-.8.68-.8 1.13a.75.75 0 01-1.5 0c0-1.23.67-1.93 1.28-2.4l.13-.1c.61-.47.8-.68.8-1.13a2 2 0 00-3.91-.48zM9 12.5a1 1 0 11-2 0 1 1 0 012 0z"></path>
|
|
26
|
+
</svg>
|
|
27
|
+
</button>
|
|
28
|
+
|
|
22
29
|
<button
|
|
23
30
|
id="pathToggle"
|
|
24
31
|
class="icon-button"
|
|
@@ -81,6 +88,74 @@
|
|
|
81
88
|
</div>
|
|
82
89
|
</section>
|
|
83
90
|
|
|
91
|
+
<div id="helpModal" class="help-modal hidden" onclick="if (event.target.id === 'helpModal') closeHelp()">
|
|
92
|
+
<div class="help-card" role="dialog" aria-modal="true" aria-labelledby="helpTitle">
|
|
93
|
+
<div class="help-head">
|
|
94
|
+
<h2 id="helpTitle">Reviewing, in four moves</h2>
|
|
95
|
+
<button class="icon-button" onclick="closeHelp()" aria-label="Close">
|
|
96
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
|
|
97
|
+
<path d="M3.72 3.72a.75.75 0 011.06 0L8 6.94l3.22-3.22a.75.75 0 111.06 1.06L9.06 8l3.22 3.22a.75.75 0 11-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 01-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 010-1.06z"></path>
|
|
98
|
+
</svg>
|
|
99
|
+
</button>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<div class="help-steps">
|
|
103
|
+
<section class="help-step">
|
|
104
|
+
<div class="help-demo demo-click" aria-hidden="true">
|
|
105
|
+
<div class="m-row"><span class="m-num">7</span><span class="m-code"></span></div>
|
|
106
|
+
<div class="m-row m-target"><span class="m-num">8</span><span class="m-code"></span></div>
|
|
107
|
+
<div class="m-row"><span class="m-num">9</span><span class="m-code"></span></div>
|
|
108
|
+
<div class="m-box"></div>
|
|
109
|
+
<div class="m-cursor"></div>
|
|
110
|
+
</div>
|
|
111
|
+
<h3>Click a line number</h3>
|
|
112
|
+
<p>That is the whole gesture. Deleted lines take comments too — often the
|
|
113
|
+
most useful note is about code that was removed.</p>
|
|
114
|
+
</section>
|
|
115
|
+
|
|
116
|
+
<section class="help-step">
|
|
117
|
+
<div class="help-demo demo-select" aria-hidden="true">
|
|
118
|
+
<div class="m-row"><span class="m-num">8</span><span class="m-code m-long"><span class="m-sel"></span></span></div>
|
|
119
|
+
<div class="m-quote"></div>
|
|
120
|
+
<div class="m-cursor"></div>
|
|
121
|
+
</div>
|
|
122
|
+
<h3>Hold <kbd>⌘</kbd> and drag to quote</h3>
|
|
123
|
+
<p>Select part of a line first, then click its number. The exact fragment
|
|
124
|
+
is quoted in the comment, so there is no ambiguity about what you meant.</p>
|
|
125
|
+
</section>
|
|
126
|
+
|
|
127
|
+
<section class="help-step">
|
|
128
|
+
<div class="help-demo demo-thread" aria-hidden="true">
|
|
129
|
+
<div class="m-comment"></div>
|
|
130
|
+
<div class="m-reply"></div>
|
|
131
|
+
</div>
|
|
132
|
+
<h3>Replies thread</h3>
|
|
133
|
+
<p>Follow up on your own comment as the code changes underneath it.
|
|
134
|
+
Everything saves as you type — there is no save button.</p>
|
|
135
|
+
</section>
|
|
136
|
+
|
|
137
|
+
<section class="help-step">
|
|
138
|
+
<div class="help-demo demo-handoff" aria-hidden="true">
|
|
139
|
+
<div class="m-term"><span class="m-prompt">$</span><span class="m-cmd"></span></div>
|
|
140
|
+
<div class="m-flow"></div>
|
|
141
|
+
<div class="m-done">✓</div>
|
|
142
|
+
</div>
|
|
143
|
+
<h3>Submit, and it goes back</h3>
|
|
144
|
+
<p>Run it as <code>reviewer | claude -p "Apply this review."</code> and
|
|
145
|
+
submitting hands the review straight to the agent, which makes the changes.</p>
|
|
146
|
+
</section>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<div class="help-foot">
|
|
150
|
+
<span class="help-keys">
|
|
151
|
+
<kbd>⌘</kbd><kbd>Enter</kbd> save · <kbd>Esc</kbd> cancel ·
|
|
152
|
+
<kbd>↑</kbd><kbd>↓</kbd> move between files
|
|
153
|
+
</span>
|
|
154
|
+
<button class="help-done" onclick="closeHelp()">Got it</button>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
84
159
|
<aside id="sidebar" class="sidebar hidden">
|
|
85
160
|
<div class="sidebar-header">
|
|
86
161
|
<h2>Changed Files</h2>
|
|
@@ -101,28 +176,6 @@
|
|
|
101
176
|
Full Context
|
|
102
177
|
</button>
|
|
103
178
|
</div>
|
|
104
|
-
<section id="howTo" class="how-to">
|
|
105
|
-
<button
|
|
106
|
-
id="howToToggle"
|
|
107
|
-
class="how-to-toggle"
|
|
108
|
-
onclick="toggleHowTo()"
|
|
109
|
-
aria-expanded="true"
|
|
110
|
-
aria-controls="howToBody">
|
|
111
|
-
<span class="how-to-chevron" aria-hidden="true">▾</span>
|
|
112
|
-
How to comment
|
|
113
|
-
</button>
|
|
114
|
-
<div id="howToBody" class="how-to-body">
|
|
115
|
-
<p><strong>Click a line number</strong> in the left column to comment on that line.
|
|
116
|
-
Removed lines work too — the most useful note is often about deleted code.</p>
|
|
117
|
-
<p><strong>Hold <kbd>⌘</kbd>/<kbd>Ctrl</kbd> and drag</strong> to select part of a line,
|
|
118
|
-
then click the line number. The selection is quoted in the comment.</p>
|
|
119
|
-
<p><kbd>⌘</kbd>/<kbd>Ctrl</kbd> + <kbd>Enter</kbd> saves ·
|
|
120
|
-
<kbd>Esc</kbd> cancels ·
|
|
121
|
-
<kbd>↑</kbd> <kbd>↓</kbd> move between files</p>
|
|
122
|
-
<p>Everything saves as you type. <strong>Submit Review</strong> when you are done.</p>
|
|
123
|
-
</div>
|
|
124
|
-
</section>
|
|
125
|
-
|
|
126
179
|
<div id="codeViewer" class="code-viewer"></div>
|
|
127
180
|
</main>
|
|
128
181
|
|
package/public/style.css
CHANGED
|
@@ -1367,6 +1367,53 @@ button:disabled {
|
|
|
1367
1367
|
align-items: flex-start;
|
|
1368
1368
|
}
|
|
1369
1369
|
|
|
1370
|
+
/* Where the review went
|
|
1371
|
+
*
|
|
1372
|
+
* The modal used to offer a download and nothing else, which is an odd thing
|
|
1373
|
+
* to put in front of somebody who started this from a terminal. The review is
|
|
1374
|
+
* already a file; knowing where is more useful than a second copy of it.
|
|
1375
|
+
*/
|
|
1376
|
+
.review-destination {
|
|
1377
|
+
display: flex;
|
|
1378
|
+
align-items: baseline;
|
|
1379
|
+
gap: 10px;
|
|
1380
|
+
margin-bottom: 14px;
|
|
1381
|
+
padding: 10px 12px;
|
|
1382
|
+
border: 1px solid #e1e4e8;
|
|
1383
|
+
border-radius: 6px;
|
|
1384
|
+
background: #f6f8fa;
|
|
1385
|
+
font-size: 13px;
|
|
1386
|
+
color: #24292e;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
.review-destination.handed-off {
|
|
1390
|
+
border-color: #b7e0c2;
|
|
1391
|
+
background: #eefcf1;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
.review-destination strong {
|
|
1395
|
+
flex: none;
|
|
1396
|
+
font-weight: 600;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
.review-destination span {
|
|
1400
|
+
min-width: 0;
|
|
1401
|
+
color: #57606a;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
.review-destination .review-path {
|
|
1405
|
+
/* Wrapped rather than truncated. There is room in a modal, and every way of
|
|
1406
|
+
* shortening a path hides the half somebody wanted: cut the front and the
|
|
1407
|
+
* directory goes, cut the back and the filename goes. */
|
|
1408
|
+
word-break: break-all;
|
|
1409
|
+
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
|
1410
|
+
font-size: 12px;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
.review-destination {
|
|
1414
|
+
align-items: flex-start;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1370
1417
|
/* How to comment
|
|
1371
1418
|
*
|
|
1372
1419
|
* Nothing on screen said that the column of line numbers was clickable, or
|
|
@@ -1448,3 +1495,365 @@ button:disabled {
|
|
|
1448
1495
|
color: #24292e;
|
|
1449
1496
|
text-align: center;
|
|
1450
1497
|
}
|
|
1498
|
+
|
|
1499
|
+
/* How to review
|
|
1500
|
+
*
|
|
1501
|
+
* This was four paragraphs pinned above the diff. It said the right things and
|
|
1502
|
+
* nobody read them, because a wall of prose is what a person skips when they
|
|
1503
|
+
* are trying to get to the code. Showing the gesture takes less attention than
|
|
1504
|
+
* describing it.
|
|
1505
|
+
*
|
|
1506
|
+
* The demos are CSS, not video. Four short clips would be a megabyte in a
|
|
1507
|
+
* package that is currently 55KB, and would be soft on a retina screen; these
|
|
1508
|
+
* are a couple of kilobytes, sharp at any density, and inherit the palette so
|
|
1509
|
+
* they keep matching the app when it changes.
|
|
1510
|
+
*/
|
|
1511
|
+
.help-modal {
|
|
1512
|
+
position: fixed;
|
|
1513
|
+
top: 0;
|
|
1514
|
+
left: 0;
|
|
1515
|
+
width: 100%;
|
|
1516
|
+
height: 100%;
|
|
1517
|
+
z-index: 1100;
|
|
1518
|
+
background: rgba(0, 0, 0, 0.5);
|
|
1519
|
+
display: flex;
|
|
1520
|
+
align-items: center;
|
|
1521
|
+
justify-content: center;
|
|
1522
|
+
padding: 32px 24px;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
.help-card {
|
|
1526
|
+
width: 100%;
|
|
1527
|
+
max-width: 720px;
|
|
1528
|
+
max-height: calc(100vh - 64px);
|
|
1529
|
+
display: flex;
|
|
1530
|
+
flex-direction: column;
|
|
1531
|
+
background: white;
|
|
1532
|
+
border-radius: 10px;
|
|
1533
|
+
box-shadow: 0 14px 48px rgba(0, 0, 0, 0.28);
|
|
1534
|
+
overflow: hidden;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
.help-head {
|
|
1538
|
+
display: flex;
|
|
1539
|
+
align-items: center;
|
|
1540
|
+
justify-content: space-between;
|
|
1541
|
+
padding: 18px 22px;
|
|
1542
|
+
border-bottom: 1px solid #e1e4e8;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
.help-head h2 {
|
|
1546
|
+
font-size: 17px;
|
|
1547
|
+
font-weight: 600;
|
|
1548
|
+
color: #24292e;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
.help-steps {
|
|
1552
|
+
display: grid;
|
|
1553
|
+
grid-template-columns: 1fr 1fr;
|
|
1554
|
+
gap: 20px;
|
|
1555
|
+
padding: 22px;
|
|
1556
|
+
overflow-y: auto;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
.help-step h3 {
|
|
1560
|
+
margin: 12px 0 4px;
|
|
1561
|
+
font-size: 13.5px;
|
|
1562
|
+
font-weight: 600;
|
|
1563
|
+
color: #24292e;
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
.help-step h3 kbd,
|
|
1567
|
+
.help-keys kbd {
|
|
1568
|
+
display: inline-block;
|
|
1569
|
+
min-width: 17px;
|
|
1570
|
+
padding: 0 5px;
|
|
1571
|
+
border: 1px solid #d1d5da;
|
|
1572
|
+
border-bottom-width: 2px;
|
|
1573
|
+
border-radius: 4px;
|
|
1574
|
+
background: #fafbfc;
|
|
1575
|
+
font-family: inherit;
|
|
1576
|
+
font-size: 11px;
|
|
1577
|
+
color: #24292e;
|
|
1578
|
+
text-align: center;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
.help-step p {
|
|
1582
|
+
font-size: 12.5px;
|
|
1583
|
+
line-height: 1.55;
|
|
1584
|
+
color: #57606a;
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
.help-step code {
|
|
1588
|
+
padding: 1px 5px;
|
|
1589
|
+
border-radius: 4px;
|
|
1590
|
+
background: #f0f2f4;
|
|
1591
|
+
font-family: 'SF Mono', Monaco, monospace;
|
|
1592
|
+
font-size: 11.5px;
|
|
1593
|
+
color: #24292e;
|
|
1594
|
+
white-space: nowrap;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
/* The demo stage. Every mock below is built from these few pieces. */
|
|
1598
|
+
.help-demo {
|
|
1599
|
+
position: relative;
|
|
1600
|
+
height: 104px;
|
|
1601
|
+
padding: 10px;
|
|
1602
|
+
border: 1px solid #e1e4e8;
|
|
1603
|
+
border-radius: 8px;
|
|
1604
|
+
background: #fbfcfd;
|
|
1605
|
+
overflow: hidden;
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
.m-row {
|
|
1609
|
+
display: flex;
|
|
1610
|
+
align-items: center;
|
|
1611
|
+
gap: 8px;
|
|
1612
|
+
height: 16px;
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
.m-num {
|
|
1616
|
+
width: 16px;
|
|
1617
|
+
border-radius: 3px;
|
|
1618
|
+
font-family: 'SF Mono', Monaco, monospace;
|
|
1619
|
+
font-size: 9px;
|
|
1620
|
+
color: #a0a6ad;
|
|
1621
|
+
text-align: right;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
.m-code {
|
|
1625
|
+
height: 5px;
|
|
1626
|
+
width: 62%;
|
|
1627
|
+
border-radius: 3px;
|
|
1628
|
+
background: #dfe3e8;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
.m-row:nth-child(2) .m-code { width: 78%; }
|
|
1632
|
+
.m-row:nth-child(3) .m-code { width: 48%; }
|
|
1633
|
+
|
|
1634
|
+
/* A pointer. One element, moved by each demo's own keyframes. */
|
|
1635
|
+
.m-cursor {
|
|
1636
|
+
position: absolute;
|
|
1637
|
+
width: 9px;
|
|
1638
|
+
height: 13px;
|
|
1639
|
+
background: #24292e;
|
|
1640
|
+
clip-path: polygon(0 0, 0 100%, 27% 74%, 46% 96%, 62% 84%, 44% 62%, 100% 58%);
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
/* 1. Click a line number */
|
|
1644
|
+
.demo-click .m-target .m-num { background: transparent; color: #0366d6; }
|
|
1645
|
+
.demo-click .m-box {
|
|
1646
|
+
position: absolute;
|
|
1647
|
+
left: 34px;
|
|
1648
|
+
top: 46px;
|
|
1649
|
+
width: 60%;
|
|
1650
|
+
height: 26px;
|
|
1651
|
+
border: 1px solid #0366d6;
|
|
1652
|
+
border-radius: 4px;
|
|
1653
|
+
background: white;
|
|
1654
|
+
opacity: 0;
|
|
1655
|
+
transform: translateY(-3px);
|
|
1656
|
+
animation: m-box-in 4s ease-in-out infinite;
|
|
1657
|
+
}
|
|
1658
|
+
.demo-click .m-target .m-num { animation: m-num-hit 4s ease-in-out infinite; }
|
|
1659
|
+
.demo-click .m-cursor { animation: m-click-path 4s ease-in-out infinite; }
|
|
1660
|
+
|
|
1661
|
+
@keyframes m-click-path {
|
|
1662
|
+
0%, 8% { left: 64px; top: 74px; opacity: 0; }
|
|
1663
|
+
18% { left: 64px; top: 74px; opacity: 1; }
|
|
1664
|
+
38%, 100% { left: 14px; top: 26px; opacity: 1; }
|
|
1665
|
+
}
|
|
1666
|
+
@keyframes m-num-hit {
|
|
1667
|
+
0%, 38% { background: transparent; color: #0366d6; }
|
|
1668
|
+
44%, 100% { background: #0366d6; color: white; }
|
|
1669
|
+
}
|
|
1670
|
+
@keyframes m-box-in {
|
|
1671
|
+
0%, 46% { opacity: 0; transform: translateY(-3px); }
|
|
1672
|
+
56%, 100% { opacity: 1; transform: translateY(0); }
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
/* 2. Hold Cmd and drag to quote */
|
|
1676
|
+
.demo-select .m-long { position: relative; width: 78%; background: #dfe3e8; }
|
|
1677
|
+
.demo-select .m-sel {
|
|
1678
|
+
position: absolute;
|
|
1679
|
+
left: 18%;
|
|
1680
|
+
top: -3px;
|
|
1681
|
+
height: 11px;
|
|
1682
|
+
width: 0;
|
|
1683
|
+
border-radius: 2px;
|
|
1684
|
+
background: rgba(3, 102, 214, 0.28);
|
|
1685
|
+
animation: m-sweep 4s ease-in-out infinite;
|
|
1686
|
+
}
|
|
1687
|
+
.demo-select .m-quote {
|
|
1688
|
+
position: absolute;
|
|
1689
|
+
left: 34px;
|
|
1690
|
+
top: 44px;
|
|
1691
|
+
width: 52%;
|
|
1692
|
+
height: 22px;
|
|
1693
|
+
border-left: 3px solid #0366d6;
|
|
1694
|
+
border-radius: 3px;
|
|
1695
|
+
background: #eef4fb;
|
|
1696
|
+
opacity: 0;
|
|
1697
|
+
animation: m-quote-in 4s ease-in-out infinite;
|
|
1698
|
+
}
|
|
1699
|
+
.demo-select .m-cursor { animation: m-drag-path 4s ease-in-out infinite; }
|
|
1700
|
+
|
|
1701
|
+
@keyframes m-sweep {
|
|
1702
|
+
0%, 14% { width: 0; }
|
|
1703
|
+
46%, 100% { width: 46%; }
|
|
1704
|
+
}
|
|
1705
|
+
@keyframes m-drag-path {
|
|
1706
|
+
0%, 14% { left: 42px; top: 12px; opacity: 1; }
|
|
1707
|
+
46% { left: 106px; top: 12px; opacity: 1; }
|
|
1708
|
+
62% { left: 22px; top: 22px; opacity: 1; }
|
|
1709
|
+
100% { left: 22px; top: 22px; opacity: 1; }
|
|
1710
|
+
}
|
|
1711
|
+
@keyframes m-quote-in {
|
|
1712
|
+
0%, 64% { opacity: 0; }
|
|
1713
|
+
74%, 100% { opacity: 1; }
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
/* 3. Replies thread */
|
|
1717
|
+
.demo-thread .m-comment,
|
|
1718
|
+
.demo-thread .m-reply {
|
|
1719
|
+
position: absolute;
|
|
1720
|
+
border-radius: 5px;
|
|
1721
|
+
background: #fff8dc;
|
|
1722
|
+
border: 1px solid #f0e0a0;
|
|
1723
|
+
}
|
|
1724
|
+
.demo-thread .m-comment { left: 12px; top: 16px; width: 72%; height: 28px; }
|
|
1725
|
+
.demo-thread .m-reply {
|
|
1726
|
+
left: 34px;
|
|
1727
|
+
top: 52px;
|
|
1728
|
+
width: 58%;
|
|
1729
|
+
height: 24px;
|
|
1730
|
+
background: white;
|
|
1731
|
+
border-color: #e1e4e8;
|
|
1732
|
+
opacity: 0;
|
|
1733
|
+
transform: translateX(-6px);
|
|
1734
|
+
animation: m-reply-in 3.6s ease-in-out infinite;
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
@keyframes m-reply-in {
|
|
1738
|
+
0%, 30% { opacity: 0; transform: translateX(-6px); }
|
|
1739
|
+
45%, 100% { opacity: 1; transform: translateX(0); }
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
/* 4. Submit, and it goes back */
|
|
1743
|
+
.demo-handoff { background: #24292e; border-color: #24292e; }
|
|
1744
|
+
.demo-handoff .m-term {
|
|
1745
|
+
display: flex;
|
|
1746
|
+
align-items: center;
|
|
1747
|
+
gap: 6px;
|
|
1748
|
+
font-family: 'SF Mono', Monaco, monospace;
|
|
1749
|
+
font-size: 9px;
|
|
1750
|
+
}
|
|
1751
|
+
.demo-handoff .m-prompt { color: #56d364; }
|
|
1752
|
+
.demo-handoff .m-cmd {
|
|
1753
|
+
height: 5px;
|
|
1754
|
+
width: 0;
|
|
1755
|
+
border-radius: 3px;
|
|
1756
|
+
background: #6e7781;
|
|
1757
|
+
animation: m-type 4s steps(14) infinite;
|
|
1758
|
+
}
|
|
1759
|
+
.demo-handoff .m-flow {
|
|
1760
|
+
position: absolute;
|
|
1761
|
+
left: 14px;
|
|
1762
|
+
top: 44px;
|
|
1763
|
+
width: 6px;
|
|
1764
|
+
height: 6px;
|
|
1765
|
+
border-radius: 50%;
|
|
1766
|
+
background: #0366d6;
|
|
1767
|
+
animation: m-flow 4s ease-in-out infinite;
|
|
1768
|
+
}
|
|
1769
|
+
.demo-handoff .m-done {
|
|
1770
|
+
position: absolute;
|
|
1771
|
+
right: 14px;
|
|
1772
|
+
top: 38px;
|
|
1773
|
+
color: #56d364;
|
|
1774
|
+
font-size: 15px;
|
|
1775
|
+
opacity: 0;
|
|
1776
|
+
animation: m-done-in 4s ease-in-out infinite;
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
@keyframes m-type {
|
|
1780
|
+
0% { width: 0; }
|
|
1781
|
+
30%, 100% { width: 74%; }
|
|
1782
|
+
}
|
|
1783
|
+
@keyframes m-flow {
|
|
1784
|
+
0%, 36% { left: 14px; opacity: 0; }
|
|
1785
|
+
44% { left: 14px; opacity: 1; }
|
|
1786
|
+
76% { left: 78%; opacity: 1; }
|
|
1787
|
+
84%, 100%{ left: 78%; opacity: 0; }
|
|
1788
|
+
}
|
|
1789
|
+
@keyframes m-done-in {
|
|
1790
|
+
0%, 78% { opacity: 0; }
|
|
1791
|
+
88%, 100% { opacity: 1; }
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
/* Someone who has asked not to see motion should not be shown four looping
|
|
1795
|
+
* animations. The mocks still read as diagrams standing still. */
|
|
1796
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1797
|
+
.help-demo * { animation: none !important; }
|
|
1798
|
+
.demo-click .m-box,
|
|
1799
|
+
.demo-select .m-quote,
|
|
1800
|
+
.demo-thread .m-reply,
|
|
1801
|
+
.demo-handoff .m-done { opacity: 1; transform: none; }
|
|
1802
|
+
.demo-select .m-sel { width: 46%; }
|
|
1803
|
+
.demo-handoff .m-cmd { width: 74%; }
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
.help-foot {
|
|
1807
|
+
display: flex;
|
|
1808
|
+
align-items: center;
|
|
1809
|
+
justify-content: space-between;
|
|
1810
|
+
gap: 16px;
|
|
1811
|
+
padding: 14px 22px;
|
|
1812
|
+
border-top: 1px solid #e1e4e8;
|
|
1813
|
+
background: #f6f8fa;
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
.help-keys {
|
|
1817
|
+
font-size: 12px;
|
|
1818
|
+
color: #57606a;
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
.help-done {
|
|
1822
|
+
flex: none;
|
|
1823
|
+
padding: 7px 18px;
|
|
1824
|
+
border: none;
|
|
1825
|
+
border-radius: 6px;
|
|
1826
|
+
background: #0366d6;
|
|
1827
|
+
color: white;
|
|
1828
|
+
font: inherit;
|
|
1829
|
+
font-size: 13px;
|
|
1830
|
+
font-weight: 500;
|
|
1831
|
+
cursor: pointer;
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
.help-done:hover { background: #0256b9; }
|
|
1835
|
+
|
|
1836
|
+
@media (max-width: 640px) {
|
|
1837
|
+
.help-steps { grid-template-columns: 1fr; }
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
/* Comments per file
|
|
1841
|
+
*
|
|
1842
|
+
* In a review of any size the question you keep asking is which files you have
|
|
1843
|
+
* already covered. The only answer on screen was a total in the sidebar
|
|
1844
|
+
* header, so you found out by clicking through every file.
|
|
1845
|
+
*/
|
|
1846
|
+
.file-comment-count {
|
|
1847
|
+
flex: none;
|
|
1848
|
+
min-width: 18px;
|
|
1849
|
+
margin-left: auto;
|
|
1850
|
+
padding: 1px 6px;
|
|
1851
|
+
border-radius: 20px;
|
|
1852
|
+
background: #fff3cd;
|
|
1853
|
+
border: 1px solid #f0e0a0;
|
|
1854
|
+
font-size: 11px;
|
|
1855
|
+
font-weight: 600;
|
|
1856
|
+
line-height: 1.5;
|
|
1857
|
+
color: #7a5d00;
|
|
1858
|
+
text-align: center;
|
|
1859
|
+
}
|