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.
@@ -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
+ }