prreviewbuddy 0.12.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 +61 -0
- package/dist/log-DdPaO6Wo.js +6640 -0
- package/dist/main.js +873 -0
- package/dist/server.js +3849 -0
- package/package.json +27 -0
- package/static/comfortaa-700.woff2 +0 -0
- package/static/icon128.png +0 -0
- package/static/reviews.js +1 -0
- package/static/shell.css +2018 -0
- package/static/sidebar.css +4802 -0
- package/static/workspace.js +0 -0
package/static/shell.css
ADDED
|
@@ -0,0 +1,2018 @@
|
|
|
1
|
+
/* PR Review Buddy workspace shell.
|
|
2
|
+
*
|
|
3
|
+
* Layered on top of sidebar.css beside it, which this page reuses wholesale for the review
|
|
4
|
+
* content itself. This file owns only the full-page furniture that a 460px column beside a GitHub
|
|
5
|
+
* diff never needed: the rail, the document pane, the assistant.
|
|
6
|
+
*
|
|
7
|
+
* The light/dark split is structural, not decorative. Dark is the operator's furniture — where you
|
|
8
|
+
* navigate and where you ask. Light is the review document. Moving between them tells you whether
|
|
9
|
+
* you are reading the review or working the tool. */
|
|
10
|
+
|
|
11
|
+
@font-face {
|
|
12
|
+
font-family: 'Comfortaa';
|
|
13
|
+
font-weight: 700;
|
|
14
|
+
font-display: swap;
|
|
15
|
+
src: url('/assets/comfortaa-700.woff2') format('woff2');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
:root {
|
|
19
|
+
--bg: #0b0f17;
|
|
20
|
+
--surface: #121826;
|
|
21
|
+
--surface-2: #1b2232;
|
|
22
|
+
--brand: #9333ea;
|
|
23
|
+
--brand-hover: #7c3aed;
|
|
24
|
+
--brand-muted: rgba(147, 51, 234, 0.15);
|
|
25
|
+
--text-primary: #f8fafc;
|
|
26
|
+
--text-secondary: #94a3b8;
|
|
27
|
+
--text-muted: #64748b;
|
|
28
|
+
--border: rgba(255, 255, 255, 0.08);
|
|
29
|
+
--danger: #f87171;
|
|
30
|
+
--warning: #fbbf24;
|
|
31
|
+
--success: #4ade80;
|
|
32
|
+
--wordmark-accent:#a855f7;
|
|
33
|
+
--wordmark-ink: var(--text-primary);
|
|
34
|
+
|
|
35
|
+
/* Scrollbars, in the brand's purple rather than the OS grey, and one pair per surface.
|
|
36
|
+
The shell is near-black and the document pane is near-white, so a single colour cannot serve
|
|
37
|
+
both: the lighter `#a855f7` is what stays legible on the dark furniture, and the deeper
|
|
38
|
+
`#9333ea` is what has contrast against the pane. Named by surface so putting one on the wrong
|
|
39
|
+
side reads as a mistake at the call site. */
|
|
40
|
+
--scroll-shell: rgba(168, 85, 247, 0.45);
|
|
41
|
+
--scroll-shell-hover: rgba(168, 85, 247, 0.75);
|
|
42
|
+
--scroll-pane: rgba(147, 51, 234, 0.32);
|
|
43
|
+
--scroll-pane-hover: rgba(147, 51, 234, 0.58);
|
|
44
|
+
--scroll-width: 8px;
|
|
45
|
+
|
|
46
|
+
--rail-width: 268px;
|
|
47
|
+
/* A proportion, not a number: the assistant is part of the workspace rather than a widget
|
|
48
|
+
docked to its edge, so it should take a share of whatever room there is. The floor keeps it
|
|
49
|
+
wide enough for a code block; the ceiling keeps the review itself the larger pane. A width
|
|
50
|
+
the reviewer drags overrides this on `.prb-body` and is remembered between sessions. */
|
|
51
|
+
--ask-width: clamp(320px, 32%, 50%);
|
|
52
|
+
--ask-min-width: 320px;
|
|
53
|
+
--header-height: 56px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
* { box-sizing: border-box; }
|
|
57
|
+
|
|
58
|
+
html, body {
|
|
59
|
+
height: 100%;
|
|
60
|
+
margin: 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* The page is exactly one viewport tall and never scrolls; the panes inside it do.
|
|
64
|
+
*
|
|
65
|
+
* A flex column rather than the `calc(100vh - header)` this used to hang on `.prb-body`. That
|
|
66
|
+
* subtraction assumed the header was the only thing above the panes, and four things can sit
|
|
67
|
+
* there: the job status, the freshness banner, an update outcome and the lineage notice. With any
|
|
68
|
+
* of them showing, the document ran taller than the viewport.
|
|
69
|
+
*
|
|
70
|
+
* `overflow: hidden` hides the scrollbar but does not stop the document being scrolled
|
|
71
|
+
* programmatically, and every theme in the rail is an anchor link. Following one scrolled the body
|
|
72
|
+
* to bring the heading into view, pushing the header and the banners off the top with no scrollbar
|
|
73
|
+
* left to bring them back. The review became unreachable above the fold, from a single click.
|
|
74
|
+
*
|
|
75
|
+
* Making the shell exactly `100%` with `min-height: 0` on the growing row means there is nothing to
|
|
76
|
+
* scroll, so the anchor resolves inside the document pane where it was always meant to. */
|
|
77
|
+
body {
|
|
78
|
+
background: var(--bg);
|
|
79
|
+
color: var(--text-primary);
|
|
80
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
81
|
+
font-size: 14px;
|
|
82
|
+
overflow: hidden;
|
|
83
|
+
display: flex;
|
|
84
|
+
flex-direction: column;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* Everything directly in the shell keeps its own height; only `.prb-body` below absorbs the rest.
|
|
88
|
+
Written structurally rather than as a list of banner classes on purpose: this broke because the
|
|
89
|
+
layout hard-coded which elements sit above the panes, and a list here would be the same mistake
|
|
90
|
+
waiting for the next banner. `<dialog>` is out of flow when open and `display: none` when not,
|
|
91
|
+
and `<script>` never renders, so neither becomes a flex item. */
|
|
92
|
+
body > * { flex: 0 0 auto; }
|
|
93
|
+
|
|
94
|
+
/* ── Scrollbars ──
|
|
95
|
+
*
|
|
96
|
+
* macOS hides scrollbars until something is actually scrolling, which on a full-height pane reads
|
|
97
|
+
* as content that simply ends. These are always drawn, so a pane that continues looks like one.
|
|
98
|
+
*
|
|
99
|
+
* 8px rather than the 6px the file list beside a diff uses. That panel is a short list in a narrow
|
|
100
|
+
* column; these are the primary surfaces of a full-page tool, holding a document that can run to
|
|
101
|
+
* two thousand pixels, and a thumb you have to aim at is a worse answer than a slightly wider one.
|
|
102
|
+
*
|
|
103
|
+
* The track stays transparent on both surfaces. A track drawn in either palette becomes a vertical
|
|
104
|
+
* rule down the edge of every pane, which is furniture nobody asked for. */
|
|
105
|
+
.prb-rail,
|
|
106
|
+
.prb-ask-log,
|
|
107
|
+
.prb-index-body {
|
|
108
|
+
scrollbar-width: thin;
|
|
109
|
+
scrollbar-color: var(--scroll-shell) transparent;
|
|
110
|
+
}
|
|
111
|
+
.prb-doc,
|
|
112
|
+
.prb-code-scroll {
|
|
113
|
+
scrollbar-width: thin;
|
|
114
|
+
scrollbar-color: var(--scroll-pane) transparent;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* WebKit wants the same thing said again, and ignores `scrollbar-color` entirely. */
|
|
118
|
+
.prb-rail::-webkit-scrollbar,
|
|
119
|
+
.prb-ask-log::-webkit-scrollbar,
|
|
120
|
+
.prb-index-body::-webkit-scrollbar,
|
|
121
|
+
.prb-doc::-webkit-scrollbar,
|
|
122
|
+
.prb-code-scroll::-webkit-scrollbar {
|
|
123
|
+
width: var(--scroll-width);
|
|
124
|
+
height: var(--scroll-width);
|
|
125
|
+
}
|
|
126
|
+
.prb-rail::-webkit-scrollbar-track,
|
|
127
|
+
.prb-ask-log::-webkit-scrollbar-track,
|
|
128
|
+
.prb-index-body::-webkit-scrollbar-track,
|
|
129
|
+
.prb-doc::-webkit-scrollbar-track,
|
|
130
|
+
.prb-code-scroll::-webkit-scrollbar-track { background: transparent; }
|
|
131
|
+
|
|
132
|
+
.prb-rail::-webkit-scrollbar-thumb,
|
|
133
|
+
.prb-ask-log::-webkit-scrollbar-thumb,
|
|
134
|
+
.prb-index-body::-webkit-scrollbar-thumb {
|
|
135
|
+
background: var(--scroll-shell);
|
|
136
|
+
border-radius: 999px;
|
|
137
|
+
}
|
|
138
|
+
.prb-rail::-webkit-scrollbar-thumb:hover,
|
|
139
|
+
.prb-ask-log::-webkit-scrollbar-thumb:hover,
|
|
140
|
+
.prb-index-body::-webkit-scrollbar-thumb:hover { background: var(--scroll-shell-hover); }
|
|
141
|
+
|
|
142
|
+
.prb-doc::-webkit-scrollbar-thumb,
|
|
143
|
+
.prb-code-scroll::-webkit-scrollbar-thumb {
|
|
144
|
+
background: var(--scroll-pane);
|
|
145
|
+
border-radius: 999px;
|
|
146
|
+
}
|
|
147
|
+
.prb-doc::-webkit-scrollbar-thumb:hover,
|
|
148
|
+
.prb-code-scroll::-webkit-scrollbar-thumb:hover { background: var(--scroll-pane-hover); }
|
|
149
|
+
|
|
150
|
+
/* The corner where two scrollbars meet, which otherwise paints in the OS's own grey. */
|
|
151
|
+
.prb-doc::-webkit-scrollbar-corner,
|
|
152
|
+
.prb-code-scroll::-webkit-scrollbar-corner,
|
|
153
|
+
.prb-rail::-webkit-scrollbar-corner,
|
|
154
|
+
.prb-ask-log::-webkit-scrollbar-corner,
|
|
155
|
+
.prb-index-body::-webkit-scrollbar-corner { background: transparent; }
|
|
156
|
+
|
|
157
|
+
/* ── Wordmark ──
|
|
158
|
+
One lowercase word, two colours, exactly as everywhere else in the product. */
|
|
159
|
+
.prb-wordmark {
|
|
160
|
+
font-family: 'Comfortaa', ui-rounded, 'SF Pro Rounded', system-ui, sans-serif;
|
|
161
|
+
font-weight: 700;
|
|
162
|
+
font-size: 15px;
|
|
163
|
+
letter-spacing: -0.01em;
|
|
164
|
+
color: var(--wordmark-ink);
|
|
165
|
+
white-space: nowrap;
|
|
166
|
+
}
|
|
167
|
+
.prb-wordmark b { color: var(--wordmark-accent); font-weight: 700; }
|
|
168
|
+
|
|
169
|
+
/* ── Header ── */
|
|
170
|
+
.prb-header {
|
|
171
|
+
height: var(--header-height);
|
|
172
|
+
display: flex;
|
|
173
|
+
align-items: center;
|
|
174
|
+
gap: 20px;
|
|
175
|
+
padding: 0 20px;
|
|
176
|
+
background: var(--surface);
|
|
177
|
+
border-bottom: 1px solid var(--border);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.prb-change-ref {
|
|
181
|
+
display: flex;
|
|
182
|
+
align-items: center;
|
|
183
|
+
gap: 8px;
|
|
184
|
+
min-width: 0;
|
|
185
|
+
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;
|
|
186
|
+
font-size: 12.5px;
|
|
187
|
+
color: var(--text-secondary);
|
|
188
|
+
}
|
|
189
|
+
.prb-change-ref strong { color: var(--text-primary); font-weight: 600; }
|
|
190
|
+
.prb-change-ref .prb-arrow { color: var(--text-muted); }
|
|
191
|
+
|
|
192
|
+
.prb-header-stats {
|
|
193
|
+
margin-left: auto;
|
|
194
|
+
display: flex;
|
|
195
|
+
align-items: center;
|
|
196
|
+
gap: 16px;
|
|
197
|
+
font-size: 12.5px;
|
|
198
|
+
color: var(--text-secondary);
|
|
199
|
+
}
|
|
200
|
+
.prb-header-stats .prb-add { color: var(--success); }
|
|
201
|
+
.prb-header-stats .prb-del { color: var(--danger); }
|
|
202
|
+
|
|
203
|
+
/* The build, and where to complain about it. Quieter than the change stats — it is provenance,
|
|
204
|
+
not something the reviewer came here to read — but never hidden, because the whole point is
|
|
205
|
+
that it survives into a screenshot. */
|
|
206
|
+
.prb-build {
|
|
207
|
+
display: flex;
|
|
208
|
+
align-items: center;
|
|
209
|
+
gap: 8px;
|
|
210
|
+
font-size: 11.5px;
|
|
211
|
+
color: var(--text-tertiary, var(--text-secondary));
|
|
212
|
+
opacity: 0.75;
|
|
213
|
+
}
|
|
214
|
+
.prb-feedback {
|
|
215
|
+
color: inherit;
|
|
216
|
+
text-decoration: underline;
|
|
217
|
+
text-underline-offset: 2px;
|
|
218
|
+
}
|
|
219
|
+
.prb-feedback:hover { color: var(--text-primary); opacity: 1; }
|
|
220
|
+
|
|
221
|
+
/* ── Stop-server dialog ── */
|
|
222
|
+
.prb-dialog {
|
|
223
|
+
max-width: 460px;
|
|
224
|
+
padding: 22px 24px;
|
|
225
|
+
border: 1px solid var(--border);
|
|
226
|
+
border-radius: 12px;
|
|
227
|
+
background: var(--surface);
|
|
228
|
+
color: var(--text-primary);
|
|
229
|
+
font: inherit;
|
|
230
|
+
}
|
|
231
|
+
.prb-dialog::backdrop { background: rgba(2, 6, 23, 0.55); }
|
|
232
|
+
.prb-dialog h2 { margin: 0 0 10px; font-size: 15px; }
|
|
233
|
+
.prb-dialog p { margin: 0 0 10px; font-size: 13px; line-height: 1.5; color: var(--text-secondary); }
|
|
234
|
+
.prb-dialog-reassure { color: var(--text-tertiary, var(--text-secondary)); }
|
|
235
|
+
.prb-dialog code { font-size: 12px; }
|
|
236
|
+
.prb-dialog-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 18px; }
|
|
237
|
+
.prb-dialog-actions button {
|
|
238
|
+
padding: 7px 14px;
|
|
239
|
+
border-radius: 8px;
|
|
240
|
+
border: 1px solid var(--border);
|
|
241
|
+
background: var(--surface-2);
|
|
242
|
+
color: var(--text-primary);
|
|
243
|
+
font: inherit;
|
|
244
|
+
font-size: 13px;
|
|
245
|
+
cursor: pointer;
|
|
246
|
+
}
|
|
247
|
+
.prb-dialog-actions button:hover { border-color: var(--brand); }
|
|
248
|
+
/* The destructive action is the one that gets the colour; the way out stays quiet. */
|
|
249
|
+
.prb-dialog-danger {
|
|
250
|
+
border-color: rgba(207, 34, 46, 0.55) !important;
|
|
251
|
+
background: rgba(207, 34, 46, 0.14) !important;
|
|
252
|
+
color: #ff9b9b !important;
|
|
253
|
+
}
|
|
254
|
+
.prb-dialog-danger:disabled { opacity: 0.6; cursor: default; }
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
/* Everything the page could still do needs the server, so the page stops pretending. */
|
|
258
|
+
.prb-stopped-notice {
|
|
259
|
+
position: sticky;
|
|
260
|
+
top: 0;
|
|
261
|
+
z-index: 20;
|
|
262
|
+
padding: 10px 18px;
|
|
263
|
+
background: #4a1d1d;
|
|
264
|
+
color: #ffd9d9;
|
|
265
|
+
font-size: 12.5px;
|
|
266
|
+
}
|
|
267
|
+
body[data-server-stopped] .prb-body { opacity: 0.55; pointer-events: none; }
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
/* ── Pull request context ──
|
|
271
|
+
The imported conversation, shown whole. This view is what makes an "already raised" claim
|
|
272
|
+
checkable rather than merely asserted, so nothing here is collapsed, elided or summarised.
|
|
273
|
+
|
|
274
|
+
Palette note: this lives in the document pane, which is light (#f8fafc) while the shell around
|
|
275
|
+
it is dark. The shell's --text-* tokens are the dark-theme ones and render white-on-white here,
|
|
276
|
+
so this block uses the pane's own literals, the same ones .prb-doc-title and .prb-file use. */
|
|
277
|
+
.prb-pr-chip {
|
|
278
|
+
flex: none;
|
|
279
|
+
padding: 1px 7px;
|
|
280
|
+
border-radius: 999px;
|
|
281
|
+
background: var(--brand-muted);
|
|
282
|
+
color: var(--wordmark-accent);
|
|
283
|
+
font-size: 11.5px;
|
|
284
|
+
font-weight: 700;
|
|
285
|
+
text-decoration: none;
|
|
286
|
+
}
|
|
287
|
+
.prb-pr-chip:hover { background: var(--brand); color: #fff; }
|
|
288
|
+
|
|
289
|
+
/* Beside the branch, after it, because the branch is what the reviewer came for and this is who
|
|
290
|
+
it belongs to. One line, ellipsed rather than wrapped: the header is a single row and a long
|
|
291
|
+
name must not push the stats onto a second. */
|
|
292
|
+
.prb-author {
|
|
293
|
+
display: inline-flex;
|
|
294
|
+
/* The sans stack, not the monospace this row is set in: a branch is a ref and a person is not. */
|
|
295
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
296
|
+
align-items: baseline;
|
|
297
|
+
gap: 5px;
|
|
298
|
+
min-width: 0;
|
|
299
|
+
color: var(--text-secondary);
|
|
300
|
+
font-size: 12px;
|
|
301
|
+
text-decoration: none;
|
|
302
|
+
}
|
|
303
|
+
.prb-author::before { content: 'by'; color: var(--text-muted); }
|
|
304
|
+
.prb-author-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
305
|
+
.prb-author-handle { color: var(--text-muted); }
|
|
306
|
+
a.prb-author:hover .prb-author-name { color: var(--text-primary); text-decoration: underline; }
|
|
307
|
+
|
|
308
|
+
.prb-pr-empty, .prb-pr-provenance, .prb-pr-truncated {
|
|
309
|
+
font-size: 13px;
|
|
310
|
+
color: #57606a;
|
|
311
|
+
line-height: 1.55;
|
|
312
|
+
}
|
|
313
|
+
.prb-pr-empty { max-width: 62ch; }
|
|
314
|
+
.prb-pr-provenance {
|
|
315
|
+
display: flex;
|
|
316
|
+
flex-wrap: wrap;
|
|
317
|
+
align-items: baseline;
|
|
318
|
+
gap: 10px;
|
|
319
|
+
margin-bottom: 22px;
|
|
320
|
+
padding-bottom: 14px;
|
|
321
|
+
border-bottom: 1px solid rgba(15, 23, 42, 0.1);
|
|
322
|
+
}
|
|
323
|
+
.prb-pr-provenance a { color: var(--brand); font-weight: 600; text-decoration: none; }
|
|
324
|
+
.prb-pr-provenance a:hover { text-decoration: underline; }
|
|
325
|
+
.prb-pr-title { color: #1f2328; }
|
|
326
|
+
.prb-pr-counts, .prb-pr-provenance time { color: #6b7280; font-size: 12px; }
|
|
327
|
+
|
|
328
|
+
/* Never quiet. A review working from a partial record has to say which part is missing. */
|
|
329
|
+
.prb-pr-truncated {
|
|
330
|
+
margin: 12px 0 0;
|
|
331
|
+
padding: 10px 12px;
|
|
332
|
+
border-left: 2px solid #cf222e;
|
|
333
|
+
background: rgba(207, 34, 46, 0.06);
|
|
334
|
+
font-size: 12.5px;
|
|
335
|
+
color: #7a1f26;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/* The feature's own promise, reported either way. Quiet, because "nothing matched" is the
|
|
339
|
+
ordinary outcome — it is silence about it that misleads. */
|
|
340
|
+
.prb-pr-attribution {
|
|
341
|
+
margin: -8px 0 20px;
|
|
342
|
+
font-size: 12.5px;
|
|
343
|
+
color: #57606a;
|
|
344
|
+
}
|
|
345
|
+
.prb-pr-attribution[data-matched='0'] { color: #6b7280; }
|
|
346
|
+
|
|
347
|
+
.prb-pr-bucket { margin-bottom: 28px; }
|
|
348
|
+
.prb-pr-bucket h4 {
|
|
349
|
+
display: flex;
|
|
350
|
+
align-items: baseline;
|
|
351
|
+
gap: 8px;
|
|
352
|
+
font-size: 13px;
|
|
353
|
+
font-weight: 600;
|
|
354
|
+
color: #1f2328;
|
|
355
|
+
}
|
|
356
|
+
.prb-pr-bucket-count { color: #6b7280; font-variant-numeric: tabular-nums; font-weight: 400; }
|
|
357
|
+
.prb-pr-bucket-note { margin: 3px 0 12px; font-size: 12px; color: #6b7280; }
|
|
358
|
+
|
|
359
|
+
.prb-pr-thread {
|
|
360
|
+
margin-bottom: 10px;
|
|
361
|
+
padding: 12px 14px;
|
|
362
|
+
border: 1px solid rgba(15, 23, 42, 0.12);
|
|
363
|
+
border-radius: 8px;
|
|
364
|
+
background: #fff;
|
|
365
|
+
}
|
|
366
|
+
.prb-pr-thread header {
|
|
367
|
+
display: flex;
|
|
368
|
+
flex-wrap: wrap;
|
|
369
|
+
align-items: baseline;
|
|
370
|
+
gap: 8px;
|
|
371
|
+
margin-bottom: 10px;
|
|
372
|
+
}
|
|
373
|
+
.prb-pr-loc {
|
|
374
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
375
|
+
font-size: 12px;
|
|
376
|
+
color: #57606a;
|
|
377
|
+
}
|
|
378
|
+
.prb-pr-flag {
|
|
379
|
+
padding: 1px 6px;
|
|
380
|
+
border-radius: 999px;
|
|
381
|
+
border: 1px solid rgba(15, 23, 42, 0.15);
|
|
382
|
+
font-size: 10px;
|
|
383
|
+
text-transform: uppercase;
|
|
384
|
+
letter-spacing: 0.04em;
|
|
385
|
+
color: #6b7280;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.prb-pr-comment + .prb-pr-comment {
|
|
389
|
+
margin-top: 10px;
|
|
390
|
+
padding-top: 10px;
|
|
391
|
+
border-top: 1px solid rgba(15, 23, 42, 0.08);
|
|
392
|
+
}
|
|
393
|
+
.prb-pr-comment-meta { display: flex; gap: 8px; align-items: baseline; font-size: 12px; }
|
|
394
|
+
.prb-pr-comment-meta a { color: var(--brand); font-weight: 600; text-decoration: none; }
|
|
395
|
+
.prb-pr-comment-meta a:hover { text-decoration: underline; }
|
|
396
|
+
.prb-pr-comment-meta time { color: #6b7280; }
|
|
397
|
+
/* Someone else's words, rendered as the Markdown they wrote rather than as one flat block. Light
|
|
398
|
+
pane, so literals throughout: see pane_palette.test.ts. */
|
|
399
|
+
.prb-pr-comment-body {
|
|
400
|
+
margin-top: 4px;
|
|
401
|
+
font-size: 13px;
|
|
402
|
+
line-height: 1.6;
|
|
403
|
+
color: #1f2328;
|
|
404
|
+
}
|
|
405
|
+
.prb-pr-md > *:first-child { margin-top: 0; }
|
|
406
|
+
.prb-pr-md > *:last-child { margin-bottom: 0; }
|
|
407
|
+
.prb-pr-md p { margin: 0 0 9px; }
|
|
408
|
+
.prb-pr-md ul, .prb-pr-md ol { margin: 0 0 9px; padding-left: 22px; }
|
|
409
|
+
.prb-pr-md li { margin: 2px 0; }
|
|
410
|
+
.prb-pr-md a { color: #8250df; }
|
|
411
|
+
.prb-pr-md strong { font-weight: 600; }
|
|
412
|
+
.prb-pr-md hr { border: 0; border-top: 1px solid rgba(15, 23, 42, 0.12); margin: 12px 0; }
|
|
413
|
+
.prb-pr-md blockquote {
|
|
414
|
+
margin: 0 0 9px;
|
|
415
|
+
padding: 2px 0 2px 12px;
|
|
416
|
+
border-left: 3px solid rgba(15, 23, 42, 0.15);
|
|
417
|
+
color: #57606a;
|
|
418
|
+
}
|
|
419
|
+
.prb-pr-md code {
|
|
420
|
+
padding: 1px 5px;
|
|
421
|
+
border-radius: 5px;
|
|
422
|
+
background: rgba(15, 23, 42, 0.06);
|
|
423
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
424
|
+
font-size: 12px;
|
|
425
|
+
}
|
|
426
|
+
/* The point of rendering Markdown at all: a pasted stack trace or snippet stays a block, scrolls
|
|
427
|
+
on its own, and never pushes the pane sideways. */
|
|
428
|
+
.prb-pr-md pre {
|
|
429
|
+
margin: 0 0 9px;
|
|
430
|
+
padding: 10px 12px;
|
|
431
|
+
border-radius: 7px;
|
|
432
|
+
border: 1px solid rgba(15, 23, 42, 0.1);
|
|
433
|
+
background: rgba(15, 23, 42, 0.04);
|
|
434
|
+
overflow-x: auto;
|
|
435
|
+
}
|
|
436
|
+
.prb-pr-md pre code { padding: 0; background: none; font-size: 12px; line-height: 1.5; }
|
|
437
|
+
.prb-pr-md .pr-helper-code-block { margin: 0 0 9px; }
|
|
438
|
+
.prb-pr-md .pr-helper-code-block > pre { margin: 0; }
|
|
439
|
+
|
|
440
|
+
/* A verdict on the whole change, drawn like the threads beside it rather than as a footnote. */
|
|
441
|
+
.prb-pr-decision header { margin-bottom: 8px; }
|
|
442
|
+
.prb-pr-decision time { color: #6b7280; font-size: 12px; }
|
|
443
|
+
.prb-pr-author { color: #1f2328; font-weight: 600; font-size: 13px; }
|
|
444
|
+
.prb-pr-verdict {
|
|
445
|
+
padding: 1px 8px;
|
|
446
|
+
border-radius: 999px;
|
|
447
|
+
border: 1px solid rgba(15, 23, 42, 0.15);
|
|
448
|
+
font-size: 11px;
|
|
449
|
+
color: #57606a;
|
|
450
|
+
}
|
|
451
|
+
.prb-pr-verdict[data-verdict='changes_requested'] {
|
|
452
|
+
color: #cf222e;
|
|
453
|
+
border-color: rgba(207, 34, 46, 0.35);
|
|
454
|
+
background: rgba(207, 34, 46, 0.07);
|
|
455
|
+
}
|
|
456
|
+
.prb-pr-verdict[data-verdict='approved'] {
|
|
457
|
+
color: #1a7f37;
|
|
458
|
+
border-color: rgba(26, 127, 55, 0.35);
|
|
459
|
+
background: rgba(26, 127, 55, 0.07);
|
|
460
|
+
}
|
|
461
|
+
.prb-pr-nobody { margin: 0; font-size: 12.5px; color: #6b7280; }
|
|
462
|
+
|
|
463
|
+
/* ── Three-pane body ── */
|
|
464
|
+
.prb-body {
|
|
465
|
+
display: grid;
|
|
466
|
+
grid-template-columns: var(--rail-width) minmax(0, 1fr) var(--ask-width);
|
|
467
|
+
/* Whatever the banners above did not take, and `0` rather than `auto` as the basis: the height
|
|
468
|
+
comes from the space left over, never from the content. With `auto` the browser measures the
|
|
469
|
+
panes first, which is how a pane full of findings ends up deciding how tall the shell is. */
|
|
470
|
+
flex: 1 1 0;
|
|
471
|
+
min-height: 0;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/* Each pane may shrink below its own content; without this it cannot, and `overflow-y: auto` on a
|
|
475
|
+
pane that is never smaller than its content has nothing to do.
|
|
476
|
+
*
|
|
477
|
+
* This used to be carried by `.prb-body` having a definite `calc()` height, which pinned the grid
|
|
478
|
+
* row and left the panes to overflow it. Once the height came from the layout instead, the default
|
|
479
|
+
* `min-height: auto` on a grid item made every pane as tall as its content and the whole page went
|
|
480
|
+
* rigid. `.prb-ask` already carried its own copy of this line, which was the clue. */
|
|
481
|
+
.prb-body > * { min-height: 0; }
|
|
482
|
+
/* No `data-ask` means "whatever fits" — wide viewports show the assistant, narrow ones do not.
|
|
483
|
+
The attribute appears only once the reviewer states a preference, and from then on it wins. */
|
|
484
|
+
.prb-body[data-ask='closed'] { grid-template-columns: var(--rail-width) minmax(0, 1fr) 0; }
|
|
485
|
+
.prb-body[data-ask='closed'] .prb-ask { display: none; }
|
|
486
|
+
|
|
487
|
+
/* ── Review path rail ──
|
|
488
|
+
The signature element. Themes are numbered because the guide genuinely is an ordered
|
|
489
|
+
walkthrough — `order` is the model's answer to "what sequence should a reviewer take" — so the
|
|
490
|
+
number carries information rather than decorating a list. */
|
|
491
|
+
.prb-rail {
|
|
492
|
+
display: flex;
|
|
493
|
+
flex-direction: column;
|
|
494
|
+
background: var(--bg);
|
|
495
|
+
border-right: 1px solid var(--border);
|
|
496
|
+
overflow-y: auto;
|
|
497
|
+
padding: 18px 0 18px;
|
|
498
|
+
}
|
|
499
|
+
/* Everything that is not this review sits at the bottom, out of the reading path. */
|
|
500
|
+
.prb-rail-foot { margin-top: auto; }
|
|
501
|
+
a.prb-rail-item { text-decoration: none; }
|
|
502
|
+
.prb-rail-item:disabled { opacity: 0.6; cursor: default; }
|
|
503
|
+
/* Operational, and it ends every open review rather than this one. It stays quiet — the
|
|
504
|
+
confirmation dialog is what makes the consequence plain — but it is not among the links. */
|
|
505
|
+
.prb-rail-danger:hover { background: var(--danger-muted, var(--surface)); color: var(--danger); }
|
|
506
|
+
.prb-rail-ops .prb-build { padding: 10px 20px 0; opacity: 0.6; }
|
|
507
|
+
|
|
508
|
+
.prb-rail-heading {
|
|
509
|
+
padding: 0 20px 8px;
|
|
510
|
+
font-size: 10.5px;
|
|
511
|
+
font-weight: 700;
|
|
512
|
+
letter-spacing: 0.09em;
|
|
513
|
+
text-transform: uppercase;
|
|
514
|
+
color: var(--text-muted);
|
|
515
|
+
}
|
|
516
|
+
.prb-rail-group + .prb-rail-group { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--border); }
|
|
517
|
+
|
|
518
|
+
/* The Overview owns the review path: the themes sit under it and fold away with it. The twisty is
|
|
519
|
+
a sibling of the Overview button, not a child, so one click navigates and the other collapses. */
|
|
520
|
+
.prb-rail-row { display: flex; align-items: stretch; }
|
|
521
|
+
.prb-rail-row .prb-rail-item { flex: 1; min-width: 0; }
|
|
522
|
+
/* The twisty sits outside the Overview button but inside its highlight: two backgrounds of
|
|
523
|
+
the same colour, so the current row reads as one row rather than as a button and a gap. */
|
|
524
|
+
.prb-rail-row:has(.prb-rail-item[aria-current='true']) { background: var(--brand-muted); }
|
|
525
|
+
.prb-rail-twisty {
|
|
526
|
+
flex: none;
|
|
527
|
+
display: flex;
|
|
528
|
+
align-items: center;
|
|
529
|
+
padding: 0 16px 0 4px;
|
|
530
|
+
background: none;
|
|
531
|
+
border: 0;
|
|
532
|
+
color: var(--text-muted);
|
|
533
|
+
cursor: pointer;
|
|
534
|
+
}
|
|
535
|
+
.prb-rail-twisty:hover { color: var(--text-primary); }
|
|
536
|
+
.prb-rail-twisty:focus-visible { outline: 2px solid var(--brand-hover); outline-offset: -2px; }
|
|
537
|
+
.prb-rail-twisty svg { width: 12px; height: 12px; transition: transform 120ms ease; }
|
|
538
|
+
.prb-rail-twisty[aria-expanded='false'] svg { transform: rotate(-90deg); }
|
|
539
|
+
.prb-rail-path { margin-top: 10px; }
|
|
540
|
+
.prb-rail-path[hidden] { display: none; }
|
|
541
|
+
|
|
542
|
+
.prb-rail-item {
|
|
543
|
+
display: flex;
|
|
544
|
+
width: 100%;
|
|
545
|
+
gap: 12px;
|
|
546
|
+
align-items: baseline;
|
|
547
|
+
padding: 9px 20px;
|
|
548
|
+
background: none;
|
|
549
|
+
border: 0;
|
|
550
|
+
border-left: 2px solid transparent;
|
|
551
|
+
text-align: left;
|
|
552
|
+
font: inherit;
|
|
553
|
+
color: var(--text-secondary);
|
|
554
|
+
cursor: pointer;
|
|
555
|
+
}
|
|
556
|
+
.prb-rail-item:hover { background: var(--surface); color: var(--text-primary); }
|
|
557
|
+
.prb-rail-item[aria-current='true'] {
|
|
558
|
+
background: var(--brand-muted);
|
|
559
|
+
border-left-color: var(--brand);
|
|
560
|
+
color: var(--text-primary);
|
|
561
|
+
}
|
|
562
|
+
.prb-rail-item:focus-visible { outline: 2px solid var(--brand-hover); outline-offset: -2px; }
|
|
563
|
+
|
|
564
|
+
.prb-rail-order {
|
|
565
|
+
flex: none;
|
|
566
|
+
width: 18px;
|
|
567
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
568
|
+
font-size: 11px;
|
|
569
|
+
color: var(--text-muted);
|
|
570
|
+
font-variant-numeric: tabular-nums;
|
|
571
|
+
}
|
|
572
|
+
.prb-rail-item[aria-current='true'] .prb-rail-order { color: var(--wordmark-accent); }
|
|
573
|
+
|
|
574
|
+
.prb-rail-label { flex: 1; min-width: 0; line-height: 1.35; }
|
|
575
|
+
.prb-rail-title { display: block; }
|
|
576
|
+
.prb-rail-note {
|
|
577
|
+
display: block;
|
|
578
|
+
margin-top: 3px;
|
|
579
|
+
font-size: 11px;
|
|
580
|
+
color: var(--text-muted);
|
|
581
|
+
}
|
|
582
|
+
.prb-rail-start {
|
|
583
|
+
display: inline-block;
|
|
584
|
+
margin-top: 4px;
|
|
585
|
+
padding: 1px 6px;
|
|
586
|
+
border-radius: 999px;
|
|
587
|
+
background: var(--brand-muted);
|
|
588
|
+
color: var(--wordmark-accent);
|
|
589
|
+
font-size: 10px;
|
|
590
|
+
font-weight: 700;
|
|
591
|
+
letter-spacing: 0.04em;
|
|
592
|
+
text-transform: uppercase;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
.prb-rail-count {
|
|
596
|
+
flex: none;
|
|
597
|
+
font-size: 11px;
|
|
598
|
+
color: var(--text-muted);
|
|
599
|
+
font-variant-numeric: tabular-nums;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/* Attention is the model's judgement, shown as a dot rather than a word so the rail stays scannable. */
|
|
603
|
+
.prb-attention {
|
|
604
|
+
flex: none;
|
|
605
|
+
width: 6px;
|
|
606
|
+
height: 6px;
|
|
607
|
+
border-radius: 50%;
|
|
608
|
+
align-self: center;
|
|
609
|
+
background: var(--text-muted);
|
|
610
|
+
}
|
|
611
|
+
.prb-attention[data-attention='high'] { background: #f87171; }
|
|
612
|
+
.prb-attention[data-attention='medium'] { background: #f0b232; }
|
|
613
|
+
.prb-attention[data-attention='low'] { background: #475569; }
|
|
614
|
+
|
|
615
|
+
/* ── Document pane ──
|
|
616
|
+
Wrapped in .pr-helper-sidebar so every reused component finds the pill tokens and the type
|
|
617
|
+
rules it was written against. The extension declares that class as a fixed 460px drawer, so the
|
|
618
|
+
box properties are undone here; the extra class outranks the single-class rules in sidebar.css. */
|
|
619
|
+
.prb-doc {
|
|
620
|
+
overflow-y: auto;
|
|
621
|
+
background: #f8fafc;
|
|
622
|
+
}
|
|
623
|
+
.pr-helper-sidebar.prb-pane {
|
|
624
|
+
position: static;
|
|
625
|
+
width: auto;
|
|
626
|
+
min-width: 0;
|
|
627
|
+
max-width: none;
|
|
628
|
+
height: auto;
|
|
629
|
+
transform: none;
|
|
630
|
+
transition: none;
|
|
631
|
+
box-shadow: none;
|
|
632
|
+
border-left: 0;
|
|
633
|
+
z-index: auto;
|
|
634
|
+
display: block;
|
|
635
|
+
padding: 28px 32px 64px;
|
|
636
|
+
max-width: 860px;
|
|
637
|
+
margin: 0 auto;
|
|
638
|
+
color: #1f2328;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
.prb-doc-title {
|
|
642
|
+
margin: 0 0 18px;
|
|
643
|
+
font-size: 19px;
|
|
644
|
+
font-weight: 600;
|
|
645
|
+
color: #1f2328;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/* A pane is present in the DOM at all times so switching costs no rebuild; only one is shown. */
|
|
649
|
+
.prb-view[hidden] { display: none; }
|
|
650
|
+
|
|
651
|
+
/* ── Disclosure strip ──
|
|
652
|
+
Anything the harness corrected or could not confirm. Amber, above the review, never buried:
|
|
653
|
+
a guide quietly repaired is a guide trusted more than it deserves. */
|
|
654
|
+
.prb-disclosure {
|
|
655
|
+
margin: 0 0 20px;
|
|
656
|
+
padding: 12px 14px;
|
|
657
|
+
border-radius: 8px;
|
|
658
|
+
border: 1px solid rgba(154, 103, 0, 0.25);
|
|
659
|
+
background: rgba(240, 178, 50, 0.12);
|
|
660
|
+
color: #7a5200;
|
|
661
|
+
font-size: 12.5px;
|
|
662
|
+
line-height: 1.55;
|
|
663
|
+
}
|
|
664
|
+
.prb-disclosure strong { font-weight: 600; }
|
|
665
|
+
.prb-disclosure ul { margin: 6px 0 0; padding-left: 18px; }
|
|
666
|
+
|
|
667
|
+
/* ── File list ── */
|
|
668
|
+
.prb-file-group { margin-bottom: 26px; }
|
|
669
|
+
.prb-file-group h4 {
|
|
670
|
+
margin: 0 0 8px;
|
|
671
|
+
font-size: 11px;
|
|
672
|
+
font-weight: 700;
|
|
673
|
+
letter-spacing: 0.07em;
|
|
674
|
+
text-transform: uppercase;
|
|
675
|
+
color: #57606a;
|
|
676
|
+
}
|
|
677
|
+
/* The path stacks, so the row centres rather than sitting on a baseline that no longer exists. */
|
|
678
|
+
.prb-file {
|
|
679
|
+
display: flex;
|
|
680
|
+
align-items: center;
|
|
681
|
+
gap: 10px;
|
|
682
|
+
padding: 8px 10px;
|
|
683
|
+
border-radius: 6px;
|
|
684
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
685
|
+
font-size: 12.5px;
|
|
686
|
+
}
|
|
687
|
+
/* Zebra striping counts DOM position, which the filter does not change — so a filtered list
|
|
688
|
+
stripes in the pattern of the list it came from and looks broken. Only the group heading is
|
|
689
|
+
ever a non-row sibling, so `.prb-file` alone carries the rhythm. */
|
|
690
|
+
.prb-file:nth-of-type(odd) { background: rgba(15, 23, 42, 0.03); }
|
|
691
|
+
.prb-file[hidden] { display: none; }
|
|
692
|
+
|
|
693
|
+
.prb-file-expandable { cursor: pointer; }
|
|
694
|
+
.prb-file-expandable:hover { background: rgba(130, 80, 223, 0.08); }
|
|
695
|
+
.prb-file-expandable:focus-visible {
|
|
696
|
+
outline: 2px solid var(--brand);
|
|
697
|
+
outline-offset: -2px;
|
|
698
|
+
}
|
|
699
|
+
.prb-file-expandable[aria-expanded='true'] {
|
|
700
|
+
background: rgba(130, 80, 223, 0.1);
|
|
701
|
+
box-shadow: inset 2px 0 0 var(--brand);
|
|
702
|
+
}
|
|
703
|
+
/* Directory above, filename below in purple, which is how every other file citation in the
|
|
704
|
+
product is drawn. The classes are the extension's own so the two cannot drift apart. */
|
|
705
|
+
.prb-file-path { flex: 1 1 auto; min-width: 0; line-height: 1.35; }
|
|
706
|
+
/* The pills are a column of their own, sitting between the buttons and the line counts rather
|
|
707
|
+
than trailing the path. Read down the list they answer one question at a glance: which files
|
|
708
|
+
have something to say about them. Trailing the path they moved with every filename length. */
|
|
709
|
+
.prb-file-badges {
|
|
710
|
+
display: flex;
|
|
711
|
+
flex: none;
|
|
712
|
+
flex-wrap: wrap;
|
|
713
|
+
align-items: center;
|
|
714
|
+
justify-content: flex-end;
|
|
715
|
+
gap: 6px;
|
|
716
|
+
min-width: 0;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/* What the path alone cannot say: a file with four issues against it and one with none look
|
|
720
|
+
identical by name, status and line count.
|
|
721
|
+
|
|
722
|
+
Palette note, and this is the third time it has bitten: everything from here to the end of the
|
|
723
|
+
Files view sits in the document pane, which is light while the shell around it is dark. The
|
|
724
|
+
--surface / --border / --text-* tokens are the shell's, so a chip styled with them renders as a
|
|
725
|
+
dark box in the middle of a light list — legible in neither theme, because it belongs to the
|
|
726
|
+
other one. Use the pane's own literals, as .prb-file-status and .prb-file-name already do. */
|
|
727
|
+
.prb-file-ref {
|
|
728
|
+
flex: none;
|
|
729
|
+
padding: 1px 6px;
|
|
730
|
+
border-radius: 999px;
|
|
731
|
+
border: 1px solid rgba(15, 23, 42, 0.15);
|
|
732
|
+
background: rgba(15, 23, 42, 0.04);
|
|
733
|
+
color: #57606a;
|
|
734
|
+
font-family: inherit;
|
|
735
|
+
font-size: 10.5px;
|
|
736
|
+
letter-spacing: 0.01em;
|
|
737
|
+
}
|
|
738
|
+
.prb-file-ref-issue {
|
|
739
|
+
border-color: rgba(207, 34, 46, 0.3);
|
|
740
|
+
background: rgba(207, 34, 46, 0.07);
|
|
741
|
+
color: #cf222e;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/* Only on the rows that cannot be opened, where there is no file viewer to carry them, so there
|
|
745
|
+
is no column of repeated buttons to keep quiet and nothing to reveal on hover. */
|
|
746
|
+
.prb-file-open {
|
|
747
|
+
flex: none;
|
|
748
|
+
padding: 2px 9px;
|
|
749
|
+
border-radius: 6px;
|
|
750
|
+
border: 1px solid rgba(15, 23, 42, 0.15);
|
|
751
|
+
background: #fff;
|
|
752
|
+
color: #57606a;
|
|
753
|
+
font: inherit;
|
|
754
|
+
font-size: 11px;
|
|
755
|
+
cursor: pointer;
|
|
756
|
+
}
|
|
757
|
+
.prb-file-open:hover { border-color: var(--brand); color: var(--brand); }
|
|
758
|
+
.prb-file-pr { text-decoration: none; line-height: 1.6; }
|
|
759
|
+
|
|
760
|
+
/* ── File search ── */
|
|
761
|
+
.prb-file-search {
|
|
762
|
+
display: flex;
|
|
763
|
+
align-items: center;
|
|
764
|
+
gap: 12px;
|
|
765
|
+
margin: 0 0 18px;
|
|
766
|
+
}
|
|
767
|
+
.prb-file-search input {
|
|
768
|
+
flex: 1;
|
|
769
|
+
min-width: 0;
|
|
770
|
+
padding: 8px 12px;
|
|
771
|
+
border-radius: 8px;
|
|
772
|
+
border: 1px solid rgba(15, 23, 42, 0.15);
|
|
773
|
+
background: #fff;
|
|
774
|
+
color: #1f2328;
|
|
775
|
+
font: inherit;
|
|
776
|
+
font-size: 13px;
|
|
777
|
+
}
|
|
778
|
+
/* Named explicitly: the placeholder inherits nothing useful, and a prompt nobody can read is a
|
|
779
|
+
search field nobody finds. */
|
|
780
|
+
.prb-file-search input::placeholder { color: #8c959f; }
|
|
781
|
+
.prb-file-search input:focus {
|
|
782
|
+
outline: none;
|
|
783
|
+
border-color: var(--brand);
|
|
784
|
+
}
|
|
785
|
+
.prb-file-search-count {
|
|
786
|
+
flex: none;
|
|
787
|
+
font-size: 11.5px;
|
|
788
|
+
color: #57606a;
|
|
789
|
+
font-variant-numeric: tabular-nums;
|
|
790
|
+
}
|
|
791
|
+
.prb-file-group[hidden] { display: none; }
|
|
792
|
+
.prb-file-none {
|
|
793
|
+
margin: 0;
|
|
794
|
+
color: #57606a;
|
|
795
|
+
font-size: 13px;
|
|
796
|
+
}
|
|
797
|
+
/* A fixed width so the counts share a right edge and the pills beside them share a left one.
|
|
798
|
+
The path absorbs the slack instead, which is why nothing here needs an auto margin. */
|
|
799
|
+
.prb-file-stat {
|
|
800
|
+
flex: none;
|
|
801
|
+
min-width: 74px;
|
|
802
|
+
text-align: right;
|
|
803
|
+
font-variant-numeric: tabular-nums;
|
|
804
|
+
font-size: 11.5px;
|
|
805
|
+
}
|
|
806
|
+
.prb-file-stat .prb-add { color: #1a7f37; }
|
|
807
|
+
.prb-file-stat .prb-del { color: #cf222e; }
|
|
808
|
+
.prb-file-status {
|
|
809
|
+
flex: none;
|
|
810
|
+
padding: 0 5px;
|
|
811
|
+
border-radius: 3px;
|
|
812
|
+
background: rgba(175, 184, 193, 0.24);
|
|
813
|
+
color: #57606a;
|
|
814
|
+
font-size: 10px;
|
|
815
|
+
text-transform: uppercase;
|
|
816
|
+
letter-spacing: 0.04em;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/* ── Ask panel ── */
|
|
820
|
+
.prb-ask {
|
|
821
|
+
position: relative;
|
|
822
|
+
display: flex;
|
|
823
|
+
flex-direction: column;
|
|
824
|
+
background: var(--surface);
|
|
825
|
+
border-left: 1px solid var(--border);
|
|
826
|
+
min-height: 0;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/* Sits over the pane's own left border rather than taking a column of its own, so the layout is
|
|
830
|
+
three panes and not five. Wider than it looks — a 3px target is a fight, and the extra width
|
|
831
|
+
costs nothing because there is nothing beneath it to click. */
|
|
832
|
+
.prb-ask-resize {
|
|
833
|
+
position: absolute;
|
|
834
|
+
top: 0;
|
|
835
|
+
bottom: 0;
|
|
836
|
+
left: -3px;
|
|
837
|
+
width: 7px;
|
|
838
|
+
cursor: col-resize;
|
|
839
|
+
z-index: 2;
|
|
840
|
+
background: transparent;
|
|
841
|
+
transition: background 120ms ease;
|
|
842
|
+
}
|
|
843
|
+
.prb-ask-resize:hover,
|
|
844
|
+
.prb-ask-resize:focus-visible,
|
|
845
|
+
.prb-body[data-resizing] .prb-ask-resize {
|
|
846
|
+
background: var(--brand);
|
|
847
|
+
outline: none;
|
|
848
|
+
}
|
|
849
|
+
/* While dragging, the pointer owns the page: without this every pass over the review below
|
|
850
|
+
selects text, and one slow drag leaves the whole change highlighted. */
|
|
851
|
+
.prb-body[data-resizing] {
|
|
852
|
+
user-select: none;
|
|
853
|
+
cursor: col-resize;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
.prb-ask-header {
|
|
857
|
+
display: flex;
|
|
858
|
+
align-items: baseline;
|
|
859
|
+
gap: 8px;
|
|
860
|
+
padding: 16px 18px 12px;
|
|
861
|
+
border-bottom: 1px solid var(--border);
|
|
862
|
+
}
|
|
863
|
+
.prb-ask-header h2 {
|
|
864
|
+
margin: 0;
|
|
865
|
+
font-size: 13px;
|
|
866
|
+
font-weight: 600;
|
|
867
|
+
color: var(--text-primary);
|
|
868
|
+
}
|
|
869
|
+
.prb-ask-scope {
|
|
870
|
+
margin-left: auto;
|
|
871
|
+
font-size: 11px;
|
|
872
|
+
color: var(--text-muted);
|
|
873
|
+
overflow: hidden;
|
|
874
|
+
text-overflow: ellipsis;
|
|
875
|
+
white-space: nowrap;
|
|
876
|
+
max-width: 150px;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
.prb-ask-log {
|
|
880
|
+
flex: 1;
|
|
881
|
+
min-height: 0;
|
|
882
|
+
overflow-y: auto;
|
|
883
|
+
/* Vertical only. Anything wide enough to scroll sideways here is a citation that has already
|
|
884
|
+
failed to be readable. */
|
|
885
|
+
overflow-x: hidden;
|
|
886
|
+
padding: 16px 18px;
|
|
887
|
+
display: flex;
|
|
888
|
+
flex-direction: column;
|
|
889
|
+
gap: 14px;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
.prb-ask-empty {
|
|
893
|
+
color: var(--text-muted);
|
|
894
|
+
font-size: 12.5px;
|
|
895
|
+
line-height: 1.6;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
.prb-suggestion {
|
|
899
|
+
display: block;
|
|
900
|
+
width: 100%;
|
|
901
|
+
margin-top: 8px;
|
|
902
|
+
padding: 9px 12px;
|
|
903
|
+
border-radius: 8px;
|
|
904
|
+
border: 1px solid var(--border);
|
|
905
|
+
background: var(--surface-2);
|
|
906
|
+
color: var(--text-secondary);
|
|
907
|
+
font: inherit;
|
|
908
|
+
font-size: 12.5px;
|
|
909
|
+
text-align: left;
|
|
910
|
+
cursor: pointer;
|
|
911
|
+
}
|
|
912
|
+
.prb-suggestion:hover { border-color: var(--brand); color: var(--text-primary); }
|
|
913
|
+
.prb-suggestion:focus-visible { outline: 2px solid var(--brand-hover); outline-offset: 2px; }
|
|
914
|
+
|
|
915
|
+
/* ── The reviewer's own prompts ──
|
|
916
|
+
A strip between the assistant's header and its conversation: reachable without scrolling the
|
|
917
|
+
log, and out of the composer, which is already carrying the scope, the field and the button.
|
|
918
|
+
Chips wrap rather than scroll — a bank is read by scanning it, and a horizontal scroller hides
|
|
919
|
+
exactly the prompts someone saved because they keep needing them. */
|
|
920
|
+
.prb-bank {
|
|
921
|
+
padding: 10px 16px 12px;
|
|
922
|
+
border-bottom: 1px solid var(--border);
|
|
923
|
+
}
|
|
924
|
+
.prb-bank-head {
|
|
925
|
+
display: flex;
|
|
926
|
+
align-items: center;
|
|
927
|
+
justify-content: space-between;
|
|
928
|
+
margin-bottom: 8px;
|
|
929
|
+
font-size: 11px;
|
|
930
|
+
letter-spacing: 0.04em;
|
|
931
|
+
text-transform: uppercase;
|
|
932
|
+
color: var(--text-tertiary, var(--text-secondary));
|
|
933
|
+
}
|
|
934
|
+
.prb-bank-add {
|
|
935
|
+
padding: 2px 8px;
|
|
936
|
+
border-radius: 6px;
|
|
937
|
+
border: 1px solid var(--border);
|
|
938
|
+
background: transparent;
|
|
939
|
+
color: var(--text-secondary);
|
|
940
|
+
font: inherit;
|
|
941
|
+
font-size: 11px;
|
|
942
|
+
letter-spacing: normal;
|
|
943
|
+
text-transform: none;
|
|
944
|
+
cursor: pointer;
|
|
945
|
+
}
|
|
946
|
+
.prb-bank-add:hover { border-color: var(--brand); color: var(--text-primary); }
|
|
947
|
+
.prb-bank-list { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
948
|
+
/* The pair reads as one chip: the label inserts, the pencil edits. They share a border so the
|
|
949
|
+
edit affordance is visibly part of the prompt it edits rather than a control floating beside it. */
|
|
950
|
+
.prb-bank-item {
|
|
951
|
+
display: inline-flex;
|
|
952
|
+
align-items: stretch;
|
|
953
|
+
border: 1px solid var(--border);
|
|
954
|
+
border-radius: 8px;
|
|
955
|
+
background: var(--surface-2);
|
|
956
|
+
overflow: hidden;
|
|
957
|
+
}
|
|
958
|
+
.prb-bank-item:hover { border-color: var(--brand); }
|
|
959
|
+
.prb-bank-use,
|
|
960
|
+
.prb-bank-edit {
|
|
961
|
+
border: 0;
|
|
962
|
+
background: transparent;
|
|
963
|
+
color: var(--text-secondary);
|
|
964
|
+
font: inherit;
|
|
965
|
+
font-size: 12px;
|
|
966
|
+
cursor: pointer;
|
|
967
|
+
}
|
|
968
|
+
.prb-bank-use { padding: 5px 4px 5px 10px; max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
969
|
+
.prb-bank-use:hover { color: var(--text-primary); }
|
|
970
|
+
.prb-bank-edit { padding: 5px 8px 5px 4px; opacity: 0.55; }
|
|
971
|
+
.prb-bank-edit:hover { opacity: 1; color: var(--text-primary); }
|
|
972
|
+
.prb-bank-use:focus-visible,
|
|
973
|
+
.prb-bank-edit:focus-visible { outline: 2px solid var(--brand-hover); outline-offset: -2px; }
|
|
974
|
+
/* Empty is a state worth showing: the heading and Add explain what the strip is for, where a
|
|
975
|
+
hidden strip leaves the whole feature undiscoverable. */
|
|
976
|
+
.prb-bank-list:empty::after {
|
|
977
|
+
content: "None yet — save a question you keep asking.";
|
|
978
|
+
font-size: 12px;
|
|
979
|
+
color: var(--text-tertiary, var(--text-secondary));
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
/* The prompt editor is wider than the shutdown dialog: it holds a paragraph, not a sentence. */
|
|
983
|
+
.prb-prompt-dialog { max-width: 560px; width: calc(100vw - 48px); }
|
|
984
|
+
.prb-field { display: block; margin-bottom: 12px; }
|
|
985
|
+
.prb-field span {
|
|
986
|
+
display: block;
|
|
987
|
+
margin-bottom: 5px;
|
|
988
|
+
font-size: 11px;
|
|
989
|
+
letter-spacing: 0.04em;
|
|
990
|
+
text-transform: uppercase;
|
|
991
|
+
color: var(--text-tertiary, var(--text-secondary));
|
|
992
|
+
}
|
|
993
|
+
.prb-field input,
|
|
994
|
+
.prb-field textarea {
|
|
995
|
+
width: 100%;
|
|
996
|
+
box-sizing: border-box;
|
|
997
|
+
padding: 8px 10px;
|
|
998
|
+
border: 1px solid var(--border);
|
|
999
|
+
border-radius: 8px;
|
|
1000
|
+
background: var(--surface-2);
|
|
1001
|
+
color: var(--text-primary);
|
|
1002
|
+
font: inherit;
|
|
1003
|
+
font-size: 13px;
|
|
1004
|
+
resize: vertical;
|
|
1005
|
+
}
|
|
1006
|
+
.prb-field input:focus,
|
|
1007
|
+
.prb-field textarea:focus { outline: none; border-color: var(--brand); }
|
|
1008
|
+
/* Delete sits hard left, away from Save. The two are not alternatives and should not look like a pair. */
|
|
1009
|
+
.prb-dialog-spacer { flex: 1; }
|
|
1010
|
+
.prb-dialog-primary {
|
|
1011
|
+
border-color: var(--brand) !important;
|
|
1012
|
+
background: var(--brand) !important;
|
|
1013
|
+
color: #ffffff !important;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
.prb-msg { font-size: 13px; line-height: 1.6; }
|
|
1017
|
+
.prb-msg-user {
|
|
1018
|
+
align-self: flex-end;
|
|
1019
|
+
max-width: 90%;
|
|
1020
|
+
overflow-wrap: anywhere;
|
|
1021
|
+
padding: 8px 12px;
|
|
1022
|
+
border-radius: 12px 12px 2px 12px;
|
|
1023
|
+
background: var(--brand);
|
|
1024
|
+
color: #fff;
|
|
1025
|
+
}
|
|
1026
|
+
.prb-msg-assistant {
|
|
1027
|
+
color: var(--text-secondary);
|
|
1028
|
+
/* The assistant is asked to cite `path/to/file.rb:42`, and a long path is one unbreakable token.
|
|
1029
|
+
Without this the panel scrolls sideways and the citation is cut off mid-path — which breaks
|
|
1030
|
+
the one thing that makes an answer checkable. `min-width: 0` because these are flex items and
|
|
1031
|
+
a flex item will not shrink below its content width on its own. */
|
|
1032
|
+
min-width: 0;
|
|
1033
|
+
overflow-wrap: anywhere;
|
|
1034
|
+
}
|
|
1035
|
+
.prb-msg-assistant code {
|
|
1036
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1037
|
+
font-size: 12px;
|
|
1038
|
+
background: var(--surface-2);
|
|
1039
|
+
padding: 1px 4px;
|
|
1040
|
+
border-radius: 4px;
|
|
1041
|
+
color: var(--text-primary);
|
|
1042
|
+
overflow-wrap: anywhere;
|
|
1043
|
+
}
|
|
1044
|
+
.prb-msg-error { color: var(--danger); }
|
|
1045
|
+
|
|
1046
|
+
/* Markdown in the assistant pane, which is dark: `.pr-helper-markdown` in the extension's stylesheet
|
|
1047
|
+
is written for the light sidebar it ships in, so the shell keeps its own. Paragraphs come from the
|
|
1048
|
+
renderer now rather than from preserved newlines, which is why the assistant bubble no longer sets
|
|
1049
|
+
white-space: pre-wrap. */
|
|
1050
|
+
.prb-md > *:first-child { margin-top: 0; }
|
|
1051
|
+
.prb-md > *:last-child { margin-bottom: 0; }
|
|
1052
|
+
.prb-md p { margin: 0 0 10px; }
|
|
1053
|
+
.prb-md ul,
|
|
1054
|
+
.prb-md ol { margin: 0 0 10px; padding-left: 18px; }
|
|
1055
|
+
.prb-md li + li { margin-top: 4px; }
|
|
1056
|
+
.prb-md strong { color: var(--text-primary); font-weight: 600; }
|
|
1057
|
+
/* A lighter purple than --brand, which is a fill colour and reads as near-black text on this pane. */
|
|
1058
|
+
.prb-md a { color: #c4b5fd; }
|
|
1059
|
+
.prb-md blockquote {
|
|
1060
|
+
margin: 0 0 10px;
|
|
1061
|
+
padding-left: 10px;
|
|
1062
|
+
border-left: 2px solid var(--border);
|
|
1063
|
+
color: var(--text-muted);
|
|
1064
|
+
}
|
|
1065
|
+
.prb-md hr { margin: 12px 0; border: none; border-top: 1px solid var(--border); }
|
|
1066
|
+
/* The block scrolls; the panel does not. A snippet with a long line must never widen the pane it
|
|
1067
|
+
sits in, and min-width:0 is what lets it shrink inside the flex column above it. */
|
|
1068
|
+
.prb-md pre {
|
|
1069
|
+
margin: 0 0 10px;
|
|
1070
|
+
padding: 9px 11px;
|
|
1071
|
+
min-width: 0;
|
|
1072
|
+
overflow-x: auto;
|
|
1073
|
+
border-radius: 6px;
|
|
1074
|
+
background: var(--surface-2);
|
|
1075
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1076
|
+
font-size: 12px;
|
|
1077
|
+
line-height: 1.5;
|
|
1078
|
+
}
|
|
1079
|
+
/* The block's own margin moved to the wrapper the copy button needs, and shell.css loads after
|
|
1080
|
+
sidebar.css, so the tie between `.prb-md pre` and `.pr-helper-code-block > pre` has to be broken
|
|
1081
|
+
here rather than left to source order. */
|
|
1082
|
+
.prb-md .pr-helper-code-block { margin: 0 0 10px; }
|
|
1083
|
+
.prb-md .pr-helper-code-block > pre { margin: 0; }
|
|
1084
|
+
|
|
1085
|
+
/* This pane is dark, so the light sidebar's button would sit on the block as a white tab. */
|
|
1086
|
+
.prb-md .pr-helper-code-copy {
|
|
1087
|
+
border-color: var(--border);
|
|
1088
|
+
background: var(--surface);
|
|
1089
|
+
color: var(--text-secondary);
|
|
1090
|
+
}
|
|
1091
|
+
.prb-md .pr-helper-code-copy:hover {
|
|
1092
|
+
border-color: #c4b5fd;
|
|
1093
|
+
color: #c4b5fd;
|
|
1094
|
+
}
|
|
1095
|
+
.prb-md .pr-helper-code-copy.copied {
|
|
1096
|
+
border-color: var(--success);
|
|
1097
|
+
color: var(--success);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
/* Inline code is a chip; inside a block that background and padding would double up. */
|
|
1101
|
+
.prb-md pre code {
|
|
1102
|
+
padding: 0;
|
|
1103
|
+
background: none;
|
|
1104
|
+
font-size: inherit;
|
|
1105
|
+
overflow-wrap: normal;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
.prb-thinking { display: flex; gap: 4px; align-items: center; color: var(--text-muted); font-size: 12.5px; }
|
|
1109
|
+
.prb-thinking i {
|
|
1110
|
+
width: 5px; height: 5px; border-radius: 50%;
|
|
1111
|
+
background: currentColor;
|
|
1112
|
+
animation: prb-pulse 1.2s ease-in-out infinite;
|
|
1113
|
+
}
|
|
1114
|
+
.prb-thinking i:nth-child(2) { animation-delay: 0.15s; }
|
|
1115
|
+
.prb-thinking i:nth-child(3) { animation-delay: 0.3s; }
|
|
1116
|
+
@keyframes prb-pulse { 0%, 100% { opacity: 0.25; } 50% { opacity: 1; } }
|
|
1117
|
+
|
|
1118
|
+
.prb-ask-form {
|
|
1119
|
+
display: flex;
|
|
1120
|
+
gap: 8px;
|
|
1121
|
+
padding: 12px 14px 14px;
|
|
1122
|
+
border-top: 1px solid var(--border);
|
|
1123
|
+
}
|
|
1124
|
+
.prb-ask-form textarea {
|
|
1125
|
+
flex: 1;
|
|
1126
|
+
min-height: 38px;
|
|
1127
|
+
max-height: 140px;
|
|
1128
|
+
padding: 9px 11px;
|
|
1129
|
+
border-radius: 8px;
|
|
1130
|
+
border: 1px solid var(--border);
|
|
1131
|
+
background: var(--bg);
|
|
1132
|
+
color: var(--text-primary);
|
|
1133
|
+
font: inherit;
|
|
1134
|
+
font-size: 13px;
|
|
1135
|
+
resize: none;
|
|
1136
|
+
}
|
|
1137
|
+
.prb-ask-form textarea:focus { outline: none; border-color: var(--brand); }
|
|
1138
|
+
.prb-ask-form button {
|
|
1139
|
+
flex: none;
|
|
1140
|
+
align-self: flex-end;
|
|
1141
|
+
padding: 9px 14px;
|
|
1142
|
+
border-radius: 8px;
|
|
1143
|
+
border: 0;
|
|
1144
|
+
background: var(--brand);
|
|
1145
|
+
color: #fff;
|
|
1146
|
+
font: inherit;
|
|
1147
|
+
font-weight: 600;
|
|
1148
|
+
cursor: pointer;
|
|
1149
|
+
}
|
|
1150
|
+
.prb-ask-form button:hover { background: var(--brand-hover); }
|
|
1151
|
+
.prb-ask-form button:disabled { opacity: 0.45; cursor: default; }
|
|
1152
|
+
|
|
1153
|
+
.prb-ask-toggle {
|
|
1154
|
+
padding: 6px 11px;
|
|
1155
|
+
border-radius: 7px;
|
|
1156
|
+
border: 1px solid var(--border);
|
|
1157
|
+
background: var(--surface-2);
|
|
1158
|
+
color: var(--text-secondary);
|
|
1159
|
+
font: inherit;
|
|
1160
|
+
font-size: 12px;
|
|
1161
|
+
cursor: pointer;
|
|
1162
|
+
}
|
|
1163
|
+
.prb-ask-toggle:hover { color: var(--text-primary); border-color: var(--brand); }
|
|
1164
|
+
|
|
1165
|
+
|
|
1166
|
+
/* Re-reading the conversation, offered on the PR context view. Light pane: literals, not tokens. */
|
|
1167
|
+
.prb-pr-refresh {
|
|
1168
|
+
display: flex;
|
|
1169
|
+
flex-wrap: wrap;
|
|
1170
|
+
align-items: center;
|
|
1171
|
+
gap: 8px 10px;
|
|
1172
|
+
margin: 0 0 16px;
|
|
1173
|
+
padding: 12px 14px;
|
|
1174
|
+
border: 1px solid #d0d7de;
|
|
1175
|
+
border-radius: 8px;
|
|
1176
|
+
background: rgba(15, 23, 42, 0.025);
|
|
1177
|
+
}
|
|
1178
|
+
.prb-pr-refresh button {
|
|
1179
|
+
padding: 5px 11px;
|
|
1180
|
+
border: 1px solid #d0d7de;
|
|
1181
|
+
border-radius: 7px;
|
|
1182
|
+
background: #fff;
|
|
1183
|
+
color: #1f2328;
|
|
1184
|
+
font: inherit;
|
|
1185
|
+
font-size: 12.5px;
|
|
1186
|
+
cursor: pointer;
|
|
1187
|
+
}
|
|
1188
|
+
.prb-pr-refresh button:hover:not(:disabled) { border-color: #8250df; color: #8250df; }
|
|
1189
|
+
.prb-pr-refresh button:disabled { opacity: 0.55; cursor: default; }
|
|
1190
|
+
/* Below the button, not beside it. Beside it, a paragraph this long squeezed the control into a
|
|
1191
|
+
narrow column and left the button floating in the middle of a wall of text. */
|
|
1192
|
+
.prb-pr-refresh-note { flex-basis: 100%; max-width: 78ch; color: #6b7280; font-size: 12.5px; line-height: 1.55; }
|
|
1193
|
+
.prb-pr-refresh-last {
|
|
1194
|
+
flex-basis: 100%;
|
|
1195
|
+
margin: 0;
|
|
1196
|
+
color: #57606a;
|
|
1197
|
+
font-size: 12.5px;
|
|
1198
|
+
line-height: 1.55;
|
|
1199
|
+
}
|
|
1200
|
+
.prb-pr-refresh-last[data-ok='false'] { color: #9a6700; }
|
|
1201
|
+
|
|
1202
|
+
/* Proof of life on a button that waits on Claude Code. currentColor, because the same spinner
|
|
1203
|
+
spins on the dark banner and in the light document pane. */
|
|
1204
|
+
.prb-spinner {
|
|
1205
|
+
display: inline-block;
|
|
1206
|
+
width: 11px;
|
|
1207
|
+
height: 11px;
|
|
1208
|
+
margin-right: 7px;
|
|
1209
|
+
vertical-align: -1px;
|
|
1210
|
+
border: 2px solid currentColor;
|
|
1211
|
+
border-right-color: transparent;
|
|
1212
|
+
border-radius: 50%;
|
|
1213
|
+
opacity: 0.8;
|
|
1214
|
+
animation: prb-spin 0.7s linear infinite;
|
|
1215
|
+
}
|
|
1216
|
+
@keyframes prb-spin { to { transform: rotate(360deg); } }
|
|
1217
|
+
/* Honours a reviewer who has asked the OS for less motion: the count beside it still ticks, so
|
|
1218
|
+
nothing is lost by holding the ring still. */
|
|
1219
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1220
|
+
.prb-spinner { animation: none; opacity: 0.5; }
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
.prb-recheck-status {
|
|
1224
|
+
flex-basis: 100%;
|
|
1225
|
+
margin-top: 6px;
|
|
1226
|
+
/* Said plainly rather than whispered. This is the only thing on screen explaining why a button
|
|
1227
|
+
has been busy for forty seconds, and inheriting a muted colour and then dimming it further
|
|
1228
|
+
left the answer nearly invisible at exactly the moment it was wanted. */
|
|
1229
|
+
color: var(--text-secondary);
|
|
1230
|
+
font-size: 12px;
|
|
1231
|
+
line-height: 1.5;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
/* A caveat above a list, in the light pane. Pane literals, not shell tokens — see
|
|
1235
|
+
pane_palette.test.ts for why this is a rule and not a preference. */
|
|
1236
|
+
/* Full width, because it introduces the cards below it and a narrower block reads as a floating
|
|
1237
|
+
aside rather than as the heading note it is. */
|
|
1238
|
+
.prb-doc-note {
|
|
1239
|
+
margin: 0 0 14px;
|
|
1240
|
+
padding: 8px 11px;
|
|
1241
|
+
border-left: 3px solid #d0d7de;
|
|
1242
|
+
border-radius: 0 6px 6px 0;
|
|
1243
|
+
background: rgba(15, 23, 42, 0.035);
|
|
1244
|
+
color: #57606a;
|
|
1245
|
+
font-size: 12.5px;
|
|
1246
|
+
line-height: 1.55;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
.prb-pr-asof {
|
|
1250
|
+
flex-basis: 100%;
|
|
1251
|
+
color: #6b7280;
|
|
1252
|
+
font-size: 12px;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
/* ── Review freshness ──
|
|
1256
|
+
Whether this review still describes the branch in front of you. Three comparisons, each with a
|
|
1257
|
+
state dot, and none of them collapsed to a yes/no — "unknown" is a frequent, honest answer here
|
|
1258
|
+
and it gets its own colour rather than borrowing either of the other two.
|
|
1259
|
+
|
|
1260
|
+
Shell furniture, not pane content: this sits between the header and the body, on the dark
|
|
1261
|
+
surface, so the shell tokens are the correct ones to use. */
|
|
1262
|
+
.prb-fresh-check {
|
|
1263
|
+
display: inline-flex;
|
|
1264
|
+
align-items: center;
|
|
1265
|
+
gap: 7px;
|
|
1266
|
+
padding: 6px 11px;
|
|
1267
|
+
border-radius: 7px;
|
|
1268
|
+
border: 1px solid var(--border);
|
|
1269
|
+
background: var(--surface-2);
|
|
1270
|
+
color: var(--text-secondary);
|
|
1271
|
+
font: inherit;
|
|
1272
|
+
font-size: 12px;
|
|
1273
|
+
cursor: pointer;
|
|
1274
|
+
}
|
|
1275
|
+
.prb-fresh-check:hover { color: var(--text-primary); border-color: var(--brand); }
|
|
1276
|
+
.prb-fresh-check::before {
|
|
1277
|
+
content: '';
|
|
1278
|
+
width: 7px;
|
|
1279
|
+
height: 7px;
|
|
1280
|
+
border-radius: 50%;
|
|
1281
|
+
background: var(--text-muted);
|
|
1282
|
+
}
|
|
1283
|
+
/* The one state worth catching an eye across a wide header. */
|
|
1284
|
+
.prb-fresh-check[data-state='changed'] {
|
|
1285
|
+
border-color: rgba(251, 191, 36, 0.45);
|
|
1286
|
+
background: rgba(251, 191, 36, 0.12);
|
|
1287
|
+
color: var(--warning);
|
|
1288
|
+
}
|
|
1289
|
+
.prb-fresh-check[data-state='changed']::before { background: var(--warning); }
|
|
1290
|
+
.prb-fresh-check[data-state='current']::before { background: var(--success); }
|
|
1291
|
+
|
|
1292
|
+
/* ── Review freshness ──
|
|
1293
|
+
*
|
|
1294
|
+
* One panel with a hierarchy, not a stack of warnings. It was three sentence-rows of equal weight,
|
|
1295
|
+
* each with its own amber dot, and it took roughly a third of the viewport to say one thing. The
|
|
1296
|
+
* order now is: what the status is, what it means, what to do, and the footnotes underneath.
|
|
1297
|
+
*
|
|
1298
|
+
* One accent, used once. Amber says "this is the state" on the rule and the dot; purple says "this
|
|
1299
|
+
* is the action" on exactly one button. Everything else is text on the surface, because a panel
|
|
1300
|
+
* where four controls are boxes is a panel with no primary action. */
|
|
1301
|
+
.prb-fresh {
|
|
1302
|
+
padding: 10px 18px 11px;
|
|
1303
|
+
border-bottom: 1px solid var(--border);
|
|
1304
|
+
border-left: 3px solid var(--text-muted);
|
|
1305
|
+
background: var(--surface);
|
|
1306
|
+
font-size: 12.5px;
|
|
1307
|
+
line-height: 1.5;
|
|
1308
|
+
}
|
|
1309
|
+
.prb-fresh[data-state='changed'] {
|
|
1310
|
+
border-left-color: var(--warning);
|
|
1311
|
+
background: linear-gradient(90deg, rgba(251, 191, 36, 0.07), rgba(251, 191, 36, 0.015));
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
/* Status and metadata on one line: a title to recognise, then facts to check, then when it was
|
|
1315
|
+
measured. Baseline-aligned so the small type sits on the title's line rather than under it. */
|
|
1316
|
+
.prb-fresh-head {
|
|
1317
|
+
display: flex;
|
|
1318
|
+
align-items: baseline;
|
|
1319
|
+
flex-wrap: wrap;
|
|
1320
|
+
gap: 4px 10px;
|
|
1321
|
+
}
|
|
1322
|
+
.prb-fresh-title { color: var(--text-primary); font-weight: 600; font-size: 13px; }
|
|
1323
|
+
.prb-fresh-facts { color: var(--text-muted); font-size: 11.5px; }
|
|
1324
|
+
.prb-fresh-facts code {
|
|
1325
|
+
padding: 0 4px;
|
|
1326
|
+
border-radius: 4px;
|
|
1327
|
+
background: var(--surface-2);
|
|
1328
|
+
color: var(--text-secondary);
|
|
1329
|
+
font-size: 11px;
|
|
1330
|
+
}
|
|
1331
|
+
.prb-fresh-dot {
|
|
1332
|
+
flex: none;
|
|
1333
|
+
align-self: center;
|
|
1334
|
+
width: 7px;
|
|
1335
|
+
height: 7px;
|
|
1336
|
+
border-radius: 50%;
|
|
1337
|
+
background: var(--text-muted);
|
|
1338
|
+
}
|
|
1339
|
+
.prb-fresh-dot[data-freshness='changed'] { background: var(--warning); }
|
|
1340
|
+
.prb-fresh-dot[data-freshness='current'] { background: var(--success); }
|
|
1341
|
+
|
|
1342
|
+
/* The consequence. One sentence, and the only prose in the panel at full contrast, because it is
|
|
1343
|
+
the sentence that decides whether the reviewer needs to act. */
|
|
1344
|
+
.prb-fresh-say {
|
|
1345
|
+
margin: 4px 0 0;
|
|
1346
|
+
color: var(--text-secondary);
|
|
1347
|
+
max-width: 110ch;
|
|
1348
|
+
}
|
|
1349
|
+
.prb-fresh-say code, .prb-fresh-notes code {
|
|
1350
|
+
padding: 0 4px;
|
|
1351
|
+
border-radius: 4px;
|
|
1352
|
+
background: var(--surface-2);
|
|
1353
|
+
color: var(--text-primary);
|
|
1354
|
+
font-size: 11.5px;
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
.prb-fresh-actions {
|
|
1358
|
+
display: flex;
|
|
1359
|
+
align-items: center;
|
|
1360
|
+
flex-wrap: wrap;
|
|
1361
|
+
gap: 6px 14px;
|
|
1362
|
+
margin-top: 8px;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
/* Everything that is not the primary action. Text, not a box: as bordered buttons these competed
|
|
1366
|
+
with Update review and the panel read as four equal choices. */
|
|
1367
|
+
.prb-fresh-quiet {
|
|
1368
|
+
padding: 2px 0;
|
|
1369
|
+
border: 0;
|
|
1370
|
+
background: none;
|
|
1371
|
+
color: var(--text-muted);
|
|
1372
|
+
font: inherit;
|
|
1373
|
+
font-size: 11.5px;
|
|
1374
|
+
text-decoration: underline;
|
|
1375
|
+
text-decoration-color: var(--border);
|
|
1376
|
+
text-underline-offset: 3px;
|
|
1377
|
+
cursor: pointer;
|
|
1378
|
+
}
|
|
1379
|
+
.prb-fresh-quiet:hover:not(:disabled) { color: var(--text-primary); text-decoration-color: var(--brand); }
|
|
1380
|
+
.prb-fresh-quiet:disabled { opacity: 0.55; cursor: default; }
|
|
1381
|
+
|
|
1382
|
+
/* Footnotes: true, worth having, and not what anyone opened the panel for. Separated by a rule
|
|
1383
|
+
rather than by space, because space here is the thing in shortest supply. */
|
|
1384
|
+
.prb-fresh-notes {
|
|
1385
|
+
margin-top: 8px;
|
|
1386
|
+
padding-top: 7px;
|
|
1387
|
+
border-top: 1px solid var(--border);
|
|
1388
|
+
display: flex;
|
|
1389
|
+
flex-direction: column;
|
|
1390
|
+
gap: 5px;
|
|
1391
|
+
}
|
|
1392
|
+
.prb-fresh-notes p {
|
|
1393
|
+
margin: 0;
|
|
1394
|
+
color: var(--text-muted);
|
|
1395
|
+
font-size: 11.5px;
|
|
1396
|
+
max-width: 120ch;
|
|
1397
|
+
}
|
|
1398
|
+
.prb-fresh-notes .prb-fresh-quiet { margin-left: 8px; }
|
|
1399
|
+
|
|
1400
|
+
/* ── Reading an older review ──
|
|
1401
|
+
Shaped like the freshness strip because it is the same kind of news: what you are reading may
|
|
1402
|
+
not be what is true now. Amber rather than red; nothing is wrong, it is just not the latest. */
|
|
1403
|
+
.prb-lineage {
|
|
1404
|
+
display: flex;
|
|
1405
|
+
align-items: center;
|
|
1406
|
+
gap: 18px;
|
|
1407
|
+
padding: 12px 18px;
|
|
1408
|
+
border-bottom: 1px solid var(--border);
|
|
1409
|
+
border-left: 3px solid var(--warning);
|
|
1410
|
+
background: linear-gradient(90deg, rgba(251, 191, 36, 0.09), rgba(251, 191, 36, 0.02));
|
|
1411
|
+
font-size: 12.5px;
|
|
1412
|
+
line-height: 1.55;
|
|
1413
|
+
}
|
|
1414
|
+
.prb-lineage p { margin: 0; flex: 1 1 auto; color: var(--text-secondary); max-width: 150ch; }
|
|
1415
|
+
.prb-lineage strong { color: var(--text-primary); font-weight: 600; }
|
|
1416
|
+
.prb-lineage-link {
|
|
1417
|
+
flex: none;
|
|
1418
|
+
padding: 5px 11px;
|
|
1419
|
+
border-radius: 7px;
|
|
1420
|
+
border: 1px solid var(--border);
|
|
1421
|
+
background: var(--surface-2);
|
|
1422
|
+
color: var(--text-secondary);
|
|
1423
|
+
font-size: 12px;
|
|
1424
|
+
text-decoration: none;
|
|
1425
|
+
}
|
|
1426
|
+
.prb-lineage-link:hover { color: var(--text-primary); border-color: var(--brand); }
|
|
1427
|
+
|
|
1428
|
+
/* ── The update action ──
|
|
1429
|
+
*
|
|
1430
|
+
* The one control in the freshness panel that changes the review, so the one thing carrying the
|
|
1431
|
+
* brand colour. Its explanation sits under it rather than beside it: a reviewer reads the button
|
|
1432
|
+
* first and the sentence only if they are undecided.
|
|
1433
|
+
*
|
|
1434
|
+
* The fresh-review command is inside a disclosure. It is the rarer answer and a raw slash command
|
|
1435
|
+
* in the open made the panel read as a console, so it is one line to open and nothing until then. */
|
|
1436
|
+
.prb-update-btn {
|
|
1437
|
+
padding: 6px 14px;
|
|
1438
|
+
border: 1px solid var(--brand);
|
|
1439
|
+
border-radius: 7px;
|
|
1440
|
+
background: var(--brand);
|
|
1441
|
+
color: #fff;
|
|
1442
|
+
font: inherit;
|
|
1443
|
+
font-size: 12px;
|
|
1444
|
+
font-weight: 600;
|
|
1445
|
+
cursor: pointer;
|
|
1446
|
+
}
|
|
1447
|
+
.prb-update-btn:hover:not(:disabled) { filter: brightness(1.08); color: #fff; }
|
|
1448
|
+
.prb-update-btn:disabled { opacity: 0.55; cursor: default; }
|
|
1449
|
+
/* Its own row under the controls, so the action line stays a line of controls. */
|
|
1450
|
+
.prb-update-explain {
|
|
1451
|
+
margin: 5px 0 0;
|
|
1452
|
+
color: var(--text-muted);
|
|
1453
|
+
font-size: 11.5px;
|
|
1454
|
+
max-width: 120ch;
|
|
1455
|
+
}
|
|
1456
|
+
.prb-update-alt { font-size: 11.5px; }
|
|
1457
|
+
/* Opened, its body drops below the controls line instead of stretching it. */
|
|
1458
|
+
.prb-update-alt[open] { flex: 1 0 100%; }
|
|
1459
|
+
.prb-update-alt > summary {
|
|
1460
|
+
display: inline-block;
|
|
1461
|
+
color: var(--text-muted);
|
|
1462
|
+
text-decoration: underline;
|
|
1463
|
+
text-decoration-color: var(--border);
|
|
1464
|
+
text-underline-offset: 3px;
|
|
1465
|
+
cursor: pointer;
|
|
1466
|
+
list-style: none;
|
|
1467
|
+
}
|
|
1468
|
+
.prb-update-alt > summary::-webkit-details-marker { display: none; }
|
|
1469
|
+
.prb-update-alt > summary:hover { color: var(--text-primary); text-decoration-color: var(--brand); }
|
|
1470
|
+
.prb-update-alt > span {
|
|
1471
|
+
display: inline-flex;
|
|
1472
|
+
flex-wrap: wrap;
|
|
1473
|
+
align-items: center;
|
|
1474
|
+
gap: 8px;
|
|
1475
|
+
margin-top: 6px;
|
|
1476
|
+
color: var(--text-muted);
|
|
1477
|
+
}
|
|
1478
|
+
.prb-update-alt code {
|
|
1479
|
+
padding: 1px 5px;
|
|
1480
|
+
border-radius: 4px;
|
|
1481
|
+
background: var(--surface-2);
|
|
1482
|
+
color: var(--text-secondary);
|
|
1483
|
+
font-size: 11px;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
/* What the last update did, on the load that follows it. Sits directly under the freshness strip
|
|
1487
|
+
and borrows its shape, because the two are answering the same question a minute apart. */
|
|
1488
|
+
.prb-update-result {
|
|
1489
|
+
display: flex;
|
|
1490
|
+
align-items: flex-start;
|
|
1491
|
+
gap: 18px;
|
|
1492
|
+
padding: 12px 18px;
|
|
1493
|
+
border-bottom: 1px solid var(--border);
|
|
1494
|
+
border-left: 3px solid var(--success);
|
|
1495
|
+
background: var(--surface);
|
|
1496
|
+
font-size: 12.5px;
|
|
1497
|
+
line-height: 1.55;
|
|
1498
|
+
}
|
|
1499
|
+
.prb-update-result[data-state='refused'],
|
|
1500
|
+
.prb-update-result[data-state='stopped'] { border-left-color: var(--warning); }
|
|
1501
|
+
.prb-update-result-body { flex: 1 1 auto; min-width: 0; }
|
|
1502
|
+
.prb-update-result p { margin: 0; color: var(--text-secondary); max-width: 150ch; }
|
|
1503
|
+
.prb-update-result strong { color: var(--text-primary); font-weight: 600; }
|
|
1504
|
+
.prb-update-result .prb-update-footnote { margin-top: 5px; color: var(--text-muted); font-size: 11.5px; }
|
|
1505
|
+
.prb-update-result .prb-fresh-dismiss {
|
|
1506
|
+
flex: none;
|
|
1507
|
+
padding: 5px 10px;
|
|
1508
|
+
border-radius: 7px;
|
|
1509
|
+
border: 1px solid var(--border);
|
|
1510
|
+
background: var(--surface-2);
|
|
1511
|
+
color: var(--text-secondary);
|
|
1512
|
+
font: inherit;
|
|
1513
|
+
font-size: 12px;
|
|
1514
|
+
cursor: pointer;
|
|
1515
|
+
}
|
|
1516
|
+
.prb-update-result .prb-fresh-dismiss:hover { color: var(--text-primary); border-color: var(--brand); }
|
|
1517
|
+
|
|
1518
|
+
/* The workspace while its review is still being made. Sits where the update result strip does,
|
|
1519
|
+
under the header, because both are answering the same question at the same spot: what is true
|
|
1520
|
+
about this review right now. A running job never carries the success colour the update strip
|
|
1521
|
+
defaults to; a failed one borrows its warning colour instead of a new one. */
|
|
1522
|
+
.prb-job {
|
|
1523
|
+
display: flex;
|
|
1524
|
+
flex-direction: column;
|
|
1525
|
+
gap: 4px;
|
|
1526
|
+
padding: 12px 18px;
|
|
1527
|
+
border-bottom: 1px solid var(--border);
|
|
1528
|
+
border-left: 3px solid var(--border);
|
|
1529
|
+
background: var(--surface);
|
|
1530
|
+
font-size: 12.5px;
|
|
1531
|
+
line-height: 1.55;
|
|
1532
|
+
color: var(--text-secondary);
|
|
1533
|
+
}
|
|
1534
|
+
.prb-job[data-job-phase='failed'] { border-left-color: var(--warning); }
|
|
1535
|
+
.prb-job p { margin: 0; }
|
|
1536
|
+
.prb-job strong { color: var(--text-primary); font-weight: 600; }
|
|
1537
|
+
.prb-job-progress { color: var(--text-muted); }
|
|
1538
|
+
.prb-job-elapsed, .prb-job-note { color: var(--text-muted); font-size: 11.5px; }
|
|
1539
|
+
.prb-job-retry {
|
|
1540
|
+
align-self: flex-start;
|
|
1541
|
+
padding: 5px 10px;
|
|
1542
|
+
border-radius: 7px;
|
|
1543
|
+
border: 1px solid var(--border);
|
|
1544
|
+
background: var(--surface-2);
|
|
1545
|
+
color: var(--text-secondary);
|
|
1546
|
+
font: inherit;
|
|
1547
|
+
font-size: 12px;
|
|
1548
|
+
cursor: pointer;
|
|
1549
|
+
}
|
|
1550
|
+
.prb-job-retry:hover { color: var(--text-primary); border-color: var(--brand); }
|
|
1551
|
+
.prb-job-retry:disabled { cursor: default; opacity: 0.6; }
|
|
1552
|
+
/* Said when a retry never reached the server, or was refused, so a button back to its resting
|
|
1553
|
+
label is not mistaken for a click that did nothing. */
|
|
1554
|
+
.prb-job-retry-status { margin: 4px 0 0; color: var(--warning); font-size: 11.5px; }
|
|
1555
|
+
|
|
1556
|
+
/* ── Responsive ──
|
|
1557
|
+
The assistant is the first thing to fold, then the rail. The review document never folds; it is
|
|
1558
|
+
the thing the page exists to show. */
|
|
1559
|
+
/* Below this the document pane is squeezed thinner than the assistant beside it, which inverts
|
|
1560
|
+
what the page is for. The assistant folds to a toggle and the review keeps the width. */
|
|
1561
|
+
@media (max-width: 1180px) {
|
|
1562
|
+
.prb-body:not([data-ask='open']) { grid-template-columns: var(--rail-width) minmax(0, 1fr) 0; }
|
|
1563
|
+
.prb-body:not([data-ask='open']) .prb-ask { display: none; }
|
|
1564
|
+
.prb-body[data-ask='open'] {
|
|
1565
|
+
grid-template-columns: var(--rail-width) minmax(0, 1fr) var(--ask-width);
|
|
1566
|
+
}
|
|
1567
|
+
.prb-body[data-ask='open'] .prb-ask { display: flex; }
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
@media (max-width: 860px) {
|
|
1571
|
+
.prb-body,
|
|
1572
|
+
.prb-body[data-ask='open'] { grid-template-columns: minmax(0, 1fr); }
|
|
1573
|
+
.prb-rail { display: none; }
|
|
1574
|
+
/* The assistant is an overlay at this width, not a column, so there is no boundary to drag. */
|
|
1575
|
+
.prb-ask-resize { display: none; }
|
|
1576
|
+
.prb-body[data-ask='open'] .prb-ask {
|
|
1577
|
+
position: fixed;
|
|
1578
|
+
inset: var(--header-height) 0 0 auto;
|
|
1579
|
+
width: min(var(--ask-width), 100%);
|
|
1580
|
+
}
|
|
1581
|
+
.pr-helper-sidebar.prb-pane { padding: 20px 18px 48px; }
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1585
|
+
*, *::before, *::after {
|
|
1586
|
+
animation-duration: 0.01ms !important;
|
|
1587
|
+
animation-iteration-count: 1 !important;
|
|
1588
|
+
transition-duration: 0.01ms !important;
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
/* ── Controls with no engine behind them in this client ──
|
|
1593
|
+
*
|
|
1594
|
+
* The reused components carry affordances for extension-only features: guided navigation over a
|
|
1595
|
+
* GitHub diff, and repository investigation. Neither exists here. A button that does nothing is
|
|
1596
|
+
* worse than an absent one — it reads as a broken product and quietly shifts the failure onto the
|
|
1597
|
+
* reviewer — so they are removed rather than left to be clicked. Everything still reachable is
|
|
1598
|
+
* wired: the Overview stat cards navigate, "Explain in simple terms" asks the assistant, guide
|
|
1599
|
+
* sections collapse, and questions can be marked answered.
|
|
1600
|
+
*
|
|
1601
|
+
* When guided navigation arrives here, delete the matching rule — do not add a handler that
|
|
1602
|
+
* apologises. */
|
|
1603
|
+
.prb-pane .pr-helper-guide-start-cta,
|
|
1604
|
+
.prb-pane .pr-helper-guide-uncomplete,
|
|
1605
|
+
.prb-pane [data-investigate-issue],
|
|
1606
|
+
.prb-pane [data-investigate-question],
|
|
1607
|
+
.prb-pane [data-add-investigation],
|
|
1608
|
+
.prb-pane .pr-helper-guide-section-actions:empty { display: none; }
|
|
1609
|
+
|
|
1610
|
+
/* File rows in the guide open the file in GitHub's diff in the extension. Here they open the code
|
|
1611
|
+
inline instead — see the inline code section below. */
|
|
1612
|
+
|
|
1613
|
+
/* ── Inline code ──
|
|
1614
|
+
*
|
|
1615
|
+
* Evidence for a claim the guide already made, opened where the claim was made. Deliberately not a
|
|
1616
|
+
* file browser: no tree, no tabs, no diff gutters. It is sized to be read and then closed. */
|
|
1617
|
+
.prb-code {
|
|
1618
|
+
margin: 10px 0 4px;
|
|
1619
|
+
border: 1px solid rgba(15, 23, 42, 0.12);
|
|
1620
|
+
border-radius: 8px;
|
|
1621
|
+
background: #fff;
|
|
1622
|
+
overflow: hidden;
|
|
1623
|
+
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
.prb-code-header {
|
|
1627
|
+
display: flex;
|
|
1628
|
+
align-items: center;
|
|
1629
|
+
gap: 12px;
|
|
1630
|
+
padding: 6px 10px;
|
|
1631
|
+
border-bottom: 1px solid rgba(15, 23, 42, 0.08);
|
|
1632
|
+
background: #f6f8fa;
|
|
1633
|
+
font-size: 11px;
|
|
1634
|
+
color: #57606a;
|
|
1635
|
+
}
|
|
1636
|
+
.prb-code-range { font-variant-numeric: tabular-nums; }
|
|
1637
|
+
|
|
1638
|
+
.prb-code-open {
|
|
1639
|
+
margin-left: auto;
|
|
1640
|
+
padding: 3px 9px;
|
|
1641
|
+
border: 1px solid rgba(15, 23, 42, 0.15);
|
|
1642
|
+
border-radius: 6px;
|
|
1643
|
+
background: #fff;
|
|
1644
|
+
color: #1f2328;
|
|
1645
|
+
font: inherit;
|
|
1646
|
+
font-size: 11px;
|
|
1647
|
+
cursor: pointer;
|
|
1648
|
+
}
|
|
1649
|
+
.prb-code-open:hover { border-color: #9333ea; color: #7c3aed; }
|
|
1650
|
+
/* Only the first of the two pushes right; the second sits beside it. */
|
|
1651
|
+
.prb-code-open ~ .prb-code-open { margin-left: 0; }
|
|
1652
|
+
/* Blue, the colour every route out to the pull request uses, so the two buttons read as two
|
|
1653
|
+
destinations rather than as one control with a variant. */
|
|
1654
|
+
.prb-code-pr {
|
|
1655
|
+
color: #0969da;
|
|
1656
|
+
text-decoration: none;
|
|
1657
|
+
}
|
|
1658
|
+
.prb-code-pr::after { content: ' \2197'; }
|
|
1659
|
+
.prb-code-pr:hover { border-color: #0969da; color: #0969da; }
|
|
1660
|
+
|
|
1661
|
+
.prb-code-scroll {
|
|
1662
|
+
max-height: 340px;
|
|
1663
|
+
overflow: auto;
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
.prb-code-table {
|
|
1667
|
+
border-collapse: collapse;
|
|
1668
|
+
width: 100%;
|
|
1669
|
+
font-size: 12px;
|
|
1670
|
+
line-height: 1.55;
|
|
1671
|
+
}
|
|
1672
|
+
.prb-code-num {
|
|
1673
|
+
width: 1%;
|
|
1674
|
+
padding: 0 10px 0 12px;
|
|
1675
|
+
text-align: right;
|
|
1676
|
+
color: #8c959f;
|
|
1677
|
+
font-variant-numeric: tabular-nums;
|
|
1678
|
+
user-select: none;
|
|
1679
|
+
white-space: nowrap;
|
|
1680
|
+
vertical-align: top;
|
|
1681
|
+
}
|
|
1682
|
+
.prb-code-line {
|
|
1683
|
+
padding-right: 12px;
|
|
1684
|
+
color: #1f2328;
|
|
1685
|
+
white-space: pre;
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
/* Orientation, not a diff. Which lines this change introduced, and nothing about what they
|
|
1689
|
+
replaced — a reviewer who wants the removals wants a diff viewer, which this is not. */
|
|
1690
|
+
.prb-code-added { background: rgba(74, 222, 128, 0.14); }
|
|
1691
|
+
.prb-code-added .prb-code-num { box-shadow: inset 2px 0 0 #1a7f37; }
|
|
1692
|
+
|
|
1693
|
+
/* The line the finding actually cited, so the eye lands there rather than at the top of a window. */
|
|
1694
|
+
.prb-code-target { background: rgba(240, 178, 50, 0.22); }
|
|
1695
|
+
.prb-code-target .prb-code-num { box-shadow: inset 2px 0 0 #9a6700; }
|
|
1696
|
+
|
|
1697
|
+
.prb-code-status,
|
|
1698
|
+
.prb-code-more {
|
|
1699
|
+
display: block;
|
|
1700
|
+
width: 100%;
|
|
1701
|
+
margin: 0;
|
|
1702
|
+
padding: 8px 12px;
|
|
1703
|
+
border: 0;
|
|
1704
|
+
border-top: 1px solid rgba(15, 23, 42, 0.08);
|
|
1705
|
+
background: #f6f8fa;
|
|
1706
|
+
color: #57606a;
|
|
1707
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
1708
|
+
font-size: 12px;
|
|
1709
|
+
text-align: left;
|
|
1710
|
+
}
|
|
1711
|
+
.prb-code-more { cursor: pointer; }
|
|
1712
|
+
.prb-code-more:hover { color: #7c3aed; }
|
|
1713
|
+
|
|
1714
|
+
/* A file row and a citation both open code now, so both have to look like they will. */
|
|
1715
|
+
.prb-pane .pr-helper-guide-file { cursor: pointer; }
|
|
1716
|
+
.prb-pane .pr-helper-issue-loc,
|
|
1717
|
+
.prb-pane .pr-helper-question-jump { cursor: pointer; }
|
|
1718
|
+
|
|
1719
|
+
/* The worktree a review came from, shown only when there is one. Two workspace tabs open on
|
|
1720
|
+
`fix/a` and `fix/a-2` are otherwise indistinguishable at a glance. */
|
|
1721
|
+
.prb-checkout {
|
|
1722
|
+
padding: 1px 6px;
|
|
1723
|
+
border-radius: 4px;
|
|
1724
|
+
background: var(--brand-muted);
|
|
1725
|
+
color: var(--wordmark-accent);
|
|
1726
|
+
font-size: 11px;
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
/* ── The reviews index ──
|
|
1730
|
+
A grid of cards, not a list of rows. A list answers "what happened, in order"; someone here is
|
|
1731
|
+
asking "which one was that", and the answer is the change itself — so the summary gets the room
|
|
1732
|
+
and the metadata lines up underneath it. Two up on desktop: enough width for two lines of
|
|
1733
|
+
summary, enough density that a morning's reviews are on one screen. */
|
|
1734
|
+
.prb-index-body { display: block; overflow: auto; }
|
|
1735
|
+
.prb-index {
|
|
1736
|
+
max-width: 1080px;
|
|
1737
|
+
margin: 0 auto;
|
|
1738
|
+
padding: 24px 24px 64px;
|
|
1739
|
+
}
|
|
1740
|
+
.prb-index-empty,
|
|
1741
|
+
.prb-index-none,
|
|
1742
|
+
.prb-index-note {
|
|
1743
|
+
margin: 0;
|
|
1744
|
+
font-size: 13px;
|
|
1745
|
+
line-height: 1.6;
|
|
1746
|
+
color: var(--text-secondary);
|
|
1747
|
+
}
|
|
1748
|
+
.prb-index-none { padding: 24px 2px; }
|
|
1749
|
+
.prb-index-note {
|
|
1750
|
+
margin-top: 28px;
|
|
1751
|
+
padding-top: 16px;
|
|
1752
|
+
border-top: 1px solid var(--border);
|
|
1753
|
+
font-size: 12px;
|
|
1754
|
+
color: var(--text-tertiary, var(--text-secondary));
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
.prb-index-controls {
|
|
1758
|
+
display: flex;
|
|
1759
|
+
gap: 12px;
|
|
1760
|
+
align-items: center;
|
|
1761
|
+
margin-bottom: 18px;
|
|
1762
|
+
}
|
|
1763
|
+
.prb-index-search {
|
|
1764
|
+
flex: 1;
|
|
1765
|
+
min-width: 0;
|
|
1766
|
+
padding: 9px 12px;
|
|
1767
|
+
border: 1px solid var(--border);
|
|
1768
|
+
border-radius: 9px;
|
|
1769
|
+
background: var(--surface-2);
|
|
1770
|
+
color: var(--text-primary);
|
|
1771
|
+
font: inherit;
|
|
1772
|
+
font-size: 13px;
|
|
1773
|
+
}
|
|
1774
|
+
.prb-index-search:focus { outline: none; border-color: var(--brand); }
|
|
1775
|
+
.prb-index-filters { display: flex; gap: 4px; flex: none; }
|
|
1776
|
+
.prb-index-filters button {
|
|
1777
|
+
padding: 7px 12px;
|
|
1778
|
+
border: 1px solid var(--border);
|
|
1779
|
+
border-radius: 8px;
|
|
1780
|
+
background: transparent;
|
|
1781
|
+
color: var(--text-secondary);
|
|
1782
|
+
font: inherit;
|
|
1783
|
+
font-size: 12.5px;
|
|
1784
|
+
cursor: pointer;
|
|
1785
|
+
}
|
|
1786
|
+
.prb-index-filters button:hover { color: var(--text-primary); border-color: var(--brand); }
|
|
1787
|
+
/* The selected filter is filled, not merely tinted: which subset you are looking at is the one
|
|
1788
|
+
thing about this bar that must be readable without comparing the buttons to each other. */
|
|
1789
|
+
.prb-index-filters button[aria-pressed="true"] {
|
|
1790
|
+
border-color: var(--brand);
|
|
1791
|
+
background: var(--brand);
|
|
1792
|
+
color: #ffffff;
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
.prb-cards {
|
|
1796
|
+
display: grid;
|
|
1797
|
+
grid-template-columns: repeat(auto-fill, minmax(420px, 1fr));
|
|
1798
|
+
gap: 14px;
|
|
1799
|
+
}
|
|
1800
|
+
.prb-card {
|
|
1801
|
+
position: relative;
|
|
1802
|
+
border: 1px solid var(--border);
|
|
1803
|
+
border-radius: 12px;
|
|
1804
|
+
background: var(--surface);
|
|
1805
|
+
}
|
|
1806
|
+
.prb-card:hover { border-color: var(--brand); }
|
|
1807
|
+
.prb-card[hidden] { display: none; }
|
|
1808
|
+
/* ── A branch's history, inside its card ──
|
|
1809
|
+
Below the card's link and separated by a rule, because it is a different kind of thing: the link
|
|
1810
|
+
above is this branch as it stands, and this is how it got there. Folded away by default and
|
|
1811
|
+
absent entirely on a branch reviewed once. */
|
|
1812
|
+
.prb-card-history { border-top: 1px solid var(--border); }
|
|
1813
|
+
.prb-card-history-toggle {
|
|
1814
|
+
display: flex;
|
|
1815
|
+
align-items: center;
|
|
1816
|
+
gap: 6px;
|
|
1817
|
+
width: 100%;
|
|
1818
|
+
padding: 8px 18px;
|
|
1819
|
+
border: 0;
|
|
1820
|
+
background: none;
|
|
1821
|
+
color: var(--text-tertiary, var(--text-secondary));
|
|
1822
|
+
font: inherit;
|
|
1823
|
+
font-size: 11.5px;
|
|
1824
|
+
font-weight: 600;
|
|
1825
|
+
text-align: left;
|
|
1826
|
+
cursor: pointer;
|
|
1827
|
+
}
|
|
1828
|
+
.prb-card-history-toggle:hover { color: var(--text-primary); }
|
|
1829
|
+
.prb-card-history-caret { transition: transform 120ms ease; }
|
|
1830
|
+
.prb-card-history-toggle[aria-expanded='true'] .prb-card-history-caret { transform: rotate(-180deg); }
|
|
1831
|
+
.prb-card-history-list {
|
|
1832
|
+
margin: 0;
|
|
1833
|
+
padding: 0 8px 8px;
|
|
1834
|
+
list-style: none;
|
|
1835
|
+
}
|
|
1836
|
+
.prb-card-history-list[hidden] { display: none; }
|
|
1837
|
+
.prb-card-history-row a {
|
|
1838
|
+
display: flex;
|
|
1839
|
+
align-items: baseline;
|
|
1840
|
+
gap: 9px;
|
|
1841
|
+
padding: 6px 10px;
|
|
1842
|
+
border-radius: 7px;
|
|
1843
|
+
color: var(--text-secondary);
|
|
1844
|
+
font-size: 12px;
|
|
1845
|
+
text-decoration: none;
|
|
1846
|
+
}
|
|
1847
|
+
.prb-card-history-row a:hover { background: var(--surface-2); color: var(--text-primary); }
|
|
1848
|
+
.prb-card-history-name { flex: none; font-weight: 600; }
|
|
1849
|
+
/* The latest is named rather than only ordered: a reviewer scanning three rows should not have to
|
|
1850
|
+
work out which end of the list is the current one. */
|
|
1851
|
+
.prb-card-history-latest {
|
|
1852
|
+
margin-left: 5px;
|
|
1853
|
+
padding: 1px 6px;
|
|
1854
|
+
border-radius: 999px;
|
|
1855
|
+
background: var(--brand);
|
|
1856
|
+
color: #fff;
|
|
1857
|
+
font-size: 9.5px;
|
|
1858
|
+
font-weight: 700;
|
|
1859
|
+
letter-spacing: 0.03em;
|
|
1860
|
+
text-transform: uppercase;
|
|
1861
|
+
}
|
|
1862
|
+
/*
|
|
1863
|
+
* Deliberately not a status chip. `Previous` is a position in the branch's history, not a state
|
|
1864
|
+
* someone left this review in, and giving it the filled treatment of `Latest` or the colour of a
|
|
1865
|
+
* workflow status would put it back into the vocabulary it was taken out of.
|
|
1866
|
+
*/
|
|
1867
|
+
.prb-card-history-previous {
|
|
1868
|
+
margin-left: 5px;
|
|
1869
|
+
padding: 1px 6px;
|
|
1870
|
+
border-radius: 999px;
|
|
1871
|
+
border: 1px solid var(--border);
|
|
1872
|
+
color: var(--text-muted);
|
|
1873
|
+
font-size: 9.5px;
|
|
1874
|
+
font-weight: 600;
|
|
1875
|
+
letter-spacing: 0.03em;
|
|
1876
|
+
text-transform: uppercase;
|
|
1877
|
+
}
|
|
1878
|
+
.prb-card-history-sha {
|
|
1879
|
+
flex: none;
|
|
1880
|
+
padding: 0 5px;
|
|
1881
|
+
border-radius: 4px;
|
|
1882
|
+
background: var(--surface-2);
|
|
1883
|
+
color: var(--text-primary);
|
|
1884
|
+
font-size: 11px;
|
|
1885
|
+
}
|
|
1886
|
+
.prb-card-history-when { flex: 1 1 auto; color: var(--text-tertiary, var(--text-secondary)); }
|
|
1887
|
+
|
|
1888
|
+
/* A pinned card is marked on its edge rather than by moving in the grid while being clicked. */
|
|
1889
|
+
.prb-card[data-pinned="true"] { box-shadow: inset 3px 0 0 var(--brand); }
|
|
1890
|
+
.prb-card-link {
|
|
1891
|
+
display: block;
|
|
1892
|
+
padding: 16px 18px 14px;
|
|
1893
|
+
text-decoration: none;
|
|
1894
|
+
color: inherit;
|
|
1895
|
+
}
|
|
1896
|
+
.prb-card-link:focus-visible { outline: 2px solid var(--brand-hover); outline-offset: -2px; border-radius: 12px; }
|
|
1897
|
+
.prb-card-head {
|
|
1898
|
+
display: flex;
|
|
1899
|
+
align-items: baseline;
|
|
1900
|
+
justify-content: space-between;
|
|
1901
|
+
gap: 12px;
|
|
1902
|
+
/* Room for the pin, which is positioned over the card's top-right corner. */
|
|
1903
|
+
padding-right: 26px;
|
|
1904
|
+
}
|
|
1905
|
+
.prb-card-head h2 {
|
|
1906
|
+
margin: 0;
|
|
1907
|
+
font-size: 14px;
|
|
1908
|
+
font-weight: 650;
|
|
1909
|
+
color: var(--text-primary);
|
|
1910
|
+
overflow: hidden;
|
|
1911
|
+
text-overflow: ellipsis;
|
|
1912
|
+
white-space: nowrap;
|
|
1913
|
+
}
|
|
1914
|
+
.prb-card-summary {
|
|
1915
|
+
margin: 7px 0 0;
|
|
1916
|
+
font-size: 12.5px;
|
|
1917
|
+
line-height: 1.55;
|
|
1918
|
+
color: var(--text-secondary);
|
|
1919
|
+
/* Two lines. The summary is the reason the card works, and the reason it cannot be allowed to
|
|
1920
|
+
set the height of every row in the grid. */
|
|
1921
|
+
display: -webkit-box;
|
|
1922
|
+
-webkit-line-clamp: 2;
|
|
1923
|
+
-webkit-box-orient: vertical;
|
|
1924
|
+
overflow: hidden;
|
|
1925
|
+
}
|
|
1926
|
+
.prb-card-where,
|
|
1927
|
+
.prb-card-stats,
|
|
1928
|
+
.prb-card-left,
|
|
1929
|
+
.prb-card-when {
|
|
1930
|
+
display: flex;
|
|
1931
|
+
flex-wrap: wrap;
|
|
1932
|
+
align-items: baseline;
|
|
1933
|
+
gap: 8px;
|
|
1934
|
+
margin: 8px 0 0;
|
|
1935
|
+
font-size: 12px;
|
|
1936
|
+
color: var(--text-tertiary, var(--text-secondary));
|
|
1937
|
+
}
|
|
1938
|
+
/* The repository is the coarser question — which project is this — so it leads. */
|
|
1939
|
+
.prb-card-repo { font-weight: 650; color: var(--text-primary); }
|
|
1940
|
+
.prb-card-branch {
|
|
1941
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1942
|
+
font-size: 11.5px;
|
|
1943
|
+
overflow: hidden;
|
|
1944
|
+
text-overflow: ellipsis;
|
|
1945
|
+
white-space: nowrap;
|
|
1946
|
+
}
|
|
1947
|
+
/* Whose work it is, after the branch: the answer to "is this mine to read" once you know which
|
|
1948
|
+
project and which branch. Muted, because it is context rather than the thing being named. */
|
|
1949
|
+
.prb-card-author {
|
|
1950
|
+
overflow: hidden;
|
|
1951
|
+
text-overflow: ellipsis;
|
|
1952
|
+
white-space: nowrap;
|
|
1953
|
+
color: var(--text-secondary);
|
|
1954
|
+
}
|
|
1955
|
+
.prb-card-author::before { content: 'by '; }
|
|
1956
|
+
|
|
1957
|
+
.prb-card-diffstat { font-variant-numeric: tabular-nums; }
|
|
1958
|
+
.prb-card-left { color: var(--text-secondary); }
|
|
1959
|
+
.prb-card-when { margin-top: 10px; }
|
|
1960
|
+
|
|
1961
|
+
.prb-card-status,
|
|
1962
|
+
.prb-card-risk {
|
|
1963
|
+
flex: none;
|
|
1964
|
+
padding: 2px 7px;
|
|
1965
|
+
border-radius: 999px;
|
|
1966
|
+
font-size: 10.5px;
|
|
1967
|
+
font-weight: 700;
|
|
1968
|
+
letter-spacing: 0.04em;
|
|
1969
|
+
text-transform: uppercase;
|
|
1970
|
+
white-space: nowrap;
|
|
1971
|
+
}
|
|
1972
|
+
.prb-card-status { border: 1px solid var(--border); color: var(--text-tertiary, var(--text-secondary)); }
|
|
1973
|
+
.prb-card-status[data-status="new"] { border-color: var(--brand); color: #c4a5f5; }
|
|
1974
|
+
.prb-card-status[data-status="complete"] { border-color: rgba(46, 160, 67, 0.5); color: #7ee2a8; }
|
|
1975
|
+
.prb-card-risk[data-risk="high"] { background: rgba(207, 34, 46, 0.16); color: #ff9b9b; }
|
|
1976
|
+
.prb-card-risk[data-risk="medium"] { background: rgba(210, 153, 34, 0.16); color: #ffd18a; }
|
|
1977
|
+
.prb-card-risk[data-risk="low"] { background: rgba(46, 160, 67, 0.14); color: #7ee2a8; }
|
|
1978
|
+
|
|
1979
|
+
/* Over the card rather than inside its link, so pinning cannot be a mis-click into the review. */
|
|
1980
|
+
.prb-card-pin {
|
|
1981
|
+
position: absolute;
|
|
1982
|
+
top: 10px;
|
|
1983
|
+
right: 10px;
|
|
1984
|
+
z-index: 1;
|
|
1985
|
+
width: 26px;
|
|
1986
|
+
height: 26px;
|
|
1987
|
+
border: 0;
|
|
1988
|
+
border-radius: 7px;
|
|
1989
|
+
background: transparent;
|
|
1990
|
+
color: var(--text-tertiary, var(--text-secondary));
|
|
1991
|
+
font-size: 14px;
|
|
1992
|
+
line-height: 1;
|
|
1993
|
+
cursor: pointer;
|
|
1994
|
+
opacity: 0.5;
|
|
1995
|
+
}
|
|
1996
|
+
.prb-card:hover .prb-card-pin,
|
|
1997
|
+
.prb-card-pin[aria-pressed="true"] { opacity: 1; }
|
|
1998
|
+
.prb-card-pin[aria-pressed="true"] { color: var(--brand); }
|
|
1999
|
+
.prb-card-pin:hover { background: var(--surface-2); }
|
|
2000
|
+
.prb-card-pin:focus-visible { outline: 2px solid var(--brand-hover); outline-offset: -2px; }
|
|
2001
|
+
|
|
2002
|
+
/* Header controls that only exist where there is a server behind them. */
|
|
2003
|
+
.prb-complete {
|
|
2004
|
+
padding: 4px 10px;
|
|
2005
|
+
border: 1px solid var(--border);
|
|
2006
|
+
border-radius: 7px;
|
|
2007
|
+
background: transparent;
|
|
2008
|
+
color: var(--text-secondary);
|
|
2009
|
+
font: inherit;
|
|
2010
|
+
font-size: 12px;
|
|
2011
|
+
cursor: pointer;
|
|
2012
|
+
}
|
|
2013
|
+
.prb-complete:hover { border-color: var(--brand); color: var(--text-primary); }
|
|
2014
|
+
.prb-complete[aria-pressed="true"] {
|
|
2015
|
+
border-color: rgba(46, 160, 67, 0.55);
|
|
2016
|
+
background: rgba(46, 160, 67, 0.14);
|
|
2017
|
+
color: #7ee2a8;
|
|
2018
|
+
}
|