semantic-code-review 0.24.2__py3-none-any.whl

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.
Files changed (100) hide show
  1. semantic_code_review/__init__.py +5 -0
  2. semantic_code_review/augment/__init__.py +3 -0
  3. semantic_code_review/augment/agents.py +95 -0
  4. semantic_code_review/augment/console.py +440 -0
  5. semantic_code_review/augment/extra_review.py +237 -0
  6. semantic_code_review/augment/fold_summary.py +447 -0
  7. semantic_code_review/augment/hunks.py +264 -0
  8. semantic_code_review/augment/mcp_server.py +156 -0
  9. semantic_code_review/augment/overview.py +233 -0
  10. semantic_code_review/augment/pass_.py +171 -0
  11. semantic_code_review/augment/pipeline.py +571 -0
  12. semantic_code_review/augment/progress.py +223 -0
  13. semantic_code_review/augment/prompts.py +93 -0
  14. semantic_code_review/augment/schemas.py +408 -0
  15. semantic_code_review/augment/tools.py +475 -0
  16. semantic_code_review/augment/trace_adapter.py +369 -0
  17. semantic_code_review/backends/__init__.py +95 -0
  18. semantic_code_review/backends/_cli_driver.py +571 -0
  19. semantic_code_review/backends/anthropic_sdk.py +56 -0
  20. semantic_code_review/backends/base.py +104 -0
  21. semantic_code_review/backends/claude_cli.py +421 -0
  22. semantic_code_review/backends/google_sdk.py +73 -0
  23. semantic_code_review/backends/openai_compat.py +42 -0
  24. semantic_code_review/cache/__init__.py +3 -0
  25. semantic_code_review/cache/store.py +87 -0
  26. semantic_code_review/cli/__init__.py +70 -0
  27. semantic_code_review/cli/_shared.py +157 -0
  28. semantic_code_review/cli/augment.py +74 -0
  29. semantic_code_review/cli/config_cmd.py +266 -0
  30. semantic_code_review/cli/fetch.py +33 -0
  31. semantic_code_review/cli/lint.py +27 -0
  32. semantic_code_review/cli/pr.py +98 -0
  33. semantic_code_review/cli/review.py +104 -0
  34. semantic_code_review/cli/runs_cmd.py +17 -0
  35. semantic_code_review/cli/show.py +19 -0
  36. semantic_code_review/cli/strip.py +20 -0
  37. semantic_code_review/config.py +563 -0
  38. semantic_code_review/config_template.py +154 -0
  39. semantic_code_review/fetch/__init__.py +61 -0
  40. semantic_code_review/fetch/anchor.py +244 -0
  41. semantic_code_review/fetch/github.py +229 -0
  42. semantic_code_review/fetch/github_comments.py +404 -0
  43. semantic_code_review/fetch/local.py +443 -0
  44. semantic_code_review/fetch/run_source.py +70 -0
  45. semantic_code_review/format/__init__.py +3 -0
  46. semantic_code_review/format/emit.py +163 -0
  47. semantic_code_review/format/lint.py +74 -0
  48. semantic_code_review/format/parse.py +574 -0
  49. semantic_code_review/format/sidecar.py +19 -0
  50. semantic_code_review/format/strip.py +20 -0
  51. semantic_code_review/git_ops.py +252 -0
  52. semantic_code_review/paths.py +61 -0
  53. semantic_code_review/review/__init__.py +3 -0
  54. semantic_code_review/review/comments.py +176 -0
  55. semantic_code_review/review/github.py +284 -0
  56. semantic_code_review/review/github_graphql.py +433 -0
  57. semantic_code_review/review/pr_flow.py +299 -0
  58. semantic_code_review/review/runner.py +407 -0
  59. semantic_code_review/review/server.py +1043 -0
  60. semantic_code_review/structural/__init__.py +41 -0
  61. semantic_code_review/structural/diff.py +102 -0
  62. semantic_code_review/structural/parse.py +330 -0
  63. semantic_code_review/structural/symbols.py +43 -0
  64. semantic_code_review/viewer/__init__.py +3 -0
  65. semantic_code_review/viewer/assets/annotations.ts +547 -0
  66. semantic_code_review/viewer/assets/boot.ts +273 -0
  67. semantic_code_review/viewer/assets/comment_store.ts +121 -0
  68. semantic_code_review/viewer/assets/comments.ts +624 -0
  69. semantic_code_review/viewer/assets/console.ts +426 -0
  70. semantic_code_review/viewer/assets/console_render.ts +213 -0
  71. semantic_code_review/viewer/assets/console_selection.ts +102 -0
  72. semantic_code_review/viewer/assets/data_store.ts +156 -0
  73. semantic_code_review/viewer/assets/file_rows.ts +49 -0
  74. semantic_code_review/viewer/assets/folds.ts +560 -0
  75. semantic_code_review/viewer/assets/index.html +62 -0
  76. semantic_code_review/viewer/assets/post_modal.ts +383 -0
  77. semantic_code_review/viewer/assets/progress.ts +138 -0
  78. semantic_code_review/viewer/assets/render.ts +937 -0
  79. semantic_code_review/viewer/assets/sidebar.ts +429 -0
  80. semantic_code_review/viewer/assets/sse.ts +78 -0
  81. semantic_code_review/viewer/assets/text_highlight.ts +215 -0
  82. semantic_code_review/viewer/assets/types.d.ts +388 -0
  83. semantic_code_review/viewer/assets/vendor/LICENSE +29 -0
  84. semantic_code_review/viewer/assets/vendor/VENDOR.md +55 -0
  85. semantic_code_review/viewer/assets/vendor/github-dark.min.css +10 -0
  86. semantic_code_review/viewer/assets/vendor/github.min.css +10 -0
  87. semantic_code_review/viewer/assets/vendor/highlight.min.js +1244 -0
  88. semantic_code_review/viewer/assets/vendor/mermaid.LICENSE +21 -0
  89. semantic_code_review/viewer/assets/vendor/mermaid.min.js +3587 -0
  90. semantic_code_review/viewer/assets/vendor/refresh.sh +65 -0
  91. semantic_code_review/viewer/assets/viewer.css +1202 -0
  92. semantic_code_review/viewer/assets/viewer.js +10588 -0
  93. semantic_code_review/viewer/build_json.py +546 -0
  94. semantic_code_review/viewer/hunk_layout.py +471 -0
  95. semantic_code_review-0.24.2.dist-info/METADATA +348 -0
  96. semantic_code_review-0.24.2.dist-info/RECORD +100 -0
  97. semantic_code_review-0.24.2.dist-info/WHEEL +5 -0
  98. semantic_code_review-0.24.2.dist-info/entry_points.txt +2 -0
  99. semantic_code_review-0.24.2.dist-info/licenses/LICENSE +21 -0
  100. semantic_code_review-0.24.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1202 @@
1
+ /* Semantic Code Review — viewer styles. */
2
+
3
+ :root {
4
+ color-scheme: dark light;
5
+ --bg: #0d1117;
6
+ --bg-alt: #161b22;
7
+ --bg-panel: #21262d;
8
+ --fg: #c9d1d9;
9
+ --fg-muted: #8b949e;
10
+ --fg-dim: #6e7681;
11
+ --border: #30363d;
12
+ --accent: #58a6ff;
13
+ --ok: #3fb950;
14
+ --warn: #d29922;
15
+ --err: #f85149;
16
+ --crit: #f85149;
17
+ --major: #d29922;
18
+ --minor: #8b949e;
19
+ --info: #58a6ff;
20
+ --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
21
+ }
22
+
23
+ @media (prefers-color-scheme: light) {
24
+ :root {
25
+ --bg: #ffffff;
26
+ --bg-alt: #f6f8fa;
27
+ --bg-panel: #eaeef2;
28
+ --fg: #1f2328;
29
+ --fg-muted: #59636e;
30
+ --fg-dim: #848d97;
31
+ --border: #d0d7de;
32
+ --accent: #0969da;
33
+ --ok: #1a7f37;
34
+ }
35
+ }
36
+
37
+ * { box-sizing: border-box; }
38
+ html, body { margin: 0; padding: 0; background: var(--bg); color: var(--fg); }
39
+ body {
40
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
41
+ font-size: 14px; line-height: 1.5;
42
+ }
43
+
44
+ /* --- Top bar -------------------------------------------------------------- */
45
+ .pr-bar {
46
+ position: sticky; top: 0; z-index: 50;
47
+ background: var(--bg-alt); border-bottom: 1px solid var(--border);
48
+ padding: 10px 16px; display: flex; gap: 14px; align-items: center;
49
+ flex-wrap: wrap;
50
+ }
51
+ .pr-title { font-weight: 600; margin-right: auto; }
52
+ .pr-title .pr-meta { color: var(--fg-muted); margin-left: 8px; font-weight: 400; font-size: 12px; }
53
+ .fold-slider { display: inline-flex; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }
54
+ .fold-slider button {
55
+ background: transparent; color: var(--fg-muted); border: none;
56
+ padding: 4px 10px; font-size: 12px; cursor: pointer;
57
+ }
58
+ .fold-slider button.active { background: var(--accent); color: #fff; }
59
+ .fold-slider button:hover:not(.active) { background: var(--bg-panel); color: var(--fg); }
60
+ .help-btn, .reset-btn {
61
+ background: transparent; border: 1px solid var(--border); color: var(--fg-muted);
62
+ padding: 4px 10px; font-size: 12px; border-radius: 6px; cursor: pointer;
63
+ }
64
+
65
+ /* --- Layout: sidebar + main ---------------------------------------------- */
66
+ .layout {
67
+ display: flex;
68
+ align-items: flex-start;
69
+ }
70
+ .group-sidebar {
71
+ flex: 0 0 220px;
72
+ position: sticky;
73
+ top: 56px; /* just below the sticky .pr-bar */
74
+ align-self: flex-start;
75
+ max-height: calc(100vh - 56px);
76
+ overflow-y: auto;
77
+ padding: 12px 10px 12px 16px;
78
+ border-right: 1px solid var(--border);
79
+ background: var(--bg);
80
+ }
81
+ .group-sidebar.empty { display: none; }
82
+ .group-sidebar #app + & { /* never triggered, but documents: sidebar left of #app */ }
83
+ .layout > main#app { flex: 1 1 auto; min-width: 0; }
84
+
85
+ .group-sidebar-header h3,
86
+ .group-axis-header h3 {
87
+ margin: 0 0 8px 2px;
88
+ font-size: 11px;
89
+ text-transform: uppercase;
90
+ letter-spacing: 0.06em;
91
+ color: var(--fg-muted);
92
+ font-weight: 600;
93
+ }
94
+
95
+ /* Each grouping axis lives in its own stacked section so reviewers
96
+ * can scan every axis at once without flipping a tab. Themes sits
97
+ * above Files; the "Show all" button sits above both axes and
98
+ * clears whichever pill is active. */
99
+ .group-axis { margin-bottom: 14px; }
100
+ .group-axis:last-child { margin-bottom: 0; }
101
+ .group-axis-header h3 { margin-top: 6px; }
102
+ .group-btn {
103
+ display: flex;
104
+ align-items: center;
105
+ gap: 6px;
106
+ width: 100%;
107
+ padding: 6px 8px;
108
+ margin-bottom: 4px;
109
+ background: transparent;
110
+ border: 1px solid transparent;
111
+ border-radius: 4px;
112
+ color: var(--fg);
113
+ font-size: 12px;
114
+ text-align: left;
115
+ cursor: pointer;
116
+ font-family: inherit;
117
+ }
118
+ .group-btn:hover { background: var(--bg-alt); border-color: var(--border); }
119
+ .group-btn.active {
120
+ background: var(--bg-panel);
121
+ border-color: var(--accent);
122
+ color: var(--fg);
123
+ }
124
+ .group-btn-label {
125
+ flex: 1 1 auto;
126
+ overflow: hidden;
127
+ text-overflow: ellipsis;
128
+ white-space: nowrap;
129
+ }
130
+ .group-btn-count {
131
+ flex: 0 0 auto;
132
+ background: var(--bg-alt);
133
+ color: var(--fg-muted);
134
+ padding: 1px 6px;
135
+ border-radius: 10px;
136
+ font-size: 11px;
137
+ min-width: 20px;
138
+ text-align: center;
139
+ }
140
+ .group-btn.active .group-btn-count {
141
+ background: var(--accent);
142
+ color: var(--bg);
143
+ }
144
+ /* Comment-thread tally on Files-axis pills: unresolved/total. Sits
145
+ * next to the hunk-count badge. Muted when every thread is resolved;
146
+ * amber when there's outstanding discussion to chase. */
147
+ .group-btn-comments {
148
+ flex: 0 0 auto;
149
+ background: var(--bg-alt);
150
+ color: var(--fg-muted);
151
+ padding: 1px 6px;
152
+ border-radius: 10px;
153
+ font-size: 11px;
154
+ min-width: 28px;
155
+ text-align: center;
156
+ }
157
+ .group-btn-comments.has-unresolved {
158
+ background: transparent;
159
+ color: var(--warn);
160
+ border: 1px solid var(--warn);
161
+ padding: 0 5px;
162
+ }
163
+ .group-btn.active .group-btn-comments {
164
+ background: var(--bg);
165
+ color: var(--fg);
166
+ }
167
+ .group-btn-all {
168
+ margin-bottom: 10px;
169
+ color: var(--fg-muted);
170
+ font-style: italic;
171
+ }
172
+
173
+ /* Symbols axis: nested class ▸ method tree. The toggle sits in a fixed
174
+ * column to the left of each pill so leaves (toggle hidden) stay aligned
175
+ * with their parents; children indent under the row. */
176
+ .group-tree-node { margin-bottom: 0; }
177
+ .group-tree-row { display: flex; align-items: center; gap: 2px; }
178
+ .group-tree-row .group-btn { margin-bottom: 4px; }
179
+ .group-tree-toggle {
180
+ flex: 0 0 auto;
181
+ width: 14px;
182
+ align-self: stretch;
183
+ margin-bottom: 4px;
184
+ padding: 0;
185
+ background: transparent;
186
+ border: none;
187
+ color: var(--fg-muted);
188
+ font-size: 9px;
189
+ line-height: 1;
190
+ cursor: pointer;
191
+ }
192
+ .group-tree-toggle:hover { color: var(--fg); }
193
+ .group-tree-toggle-leaf { cursor: default; visibility: hidden; }
194
+ .group-tree-children { padding-left: 12px; }
195
+
196
+ /* Subtle tell for hunks that no semantic group claimed, visible in
197
+ * the unfiltered view only. Left border gets a dotted treatment so
198
+ * the eye can pick them out at a glance without them feeling alarming. */
199
+ .hunk.ungrouped {
200
+ border-left-style: dotted !important;
201
+ }
202
+
203
+ /* --- PR overview panel ---------------------------------------------------- */
204
+ .pr-panel {
205
+ margin: 12px 16px; background: var(--bg-alt); border: 1px solid var(--border);
206
+ border-radius: 8px; padding: 12px 16px;
207
+ }
208
+ .pr-panel h2 { margin: 0 0 6px 0; font-size: 14px; font-weight: 600; }
209
+ .pr-panel .themes span {
210
+ display: inline-block; background: var(--bg-panel); color: var(--fg-muted);
211
+ padding: 2px 8px; border-radius: 10px; font-size: 11px; margin-right: 4px;
212
+ }
213
+
214
+ /* --- Files ---------------------------------------------------------------- */
215
+ .file {
216
+ margin: 12px 16px; background: var(--bg-alt); border: 1px solid var(--border);
217
+ border-radius: 8px; overflow: hidden;
218
+ }
219
+ .file-header {
220
+ display: flex; align-items: center; gap: 10px; padding: 10px 14px; cursor: pointer;
221
+ background: var(--bg-panel); user-select: none;
222
+ }
223
+ .file.folded .file-header { background: var(--bg-alt); }
224
+ .chevron {
225
+ width: 14px; height: 14px;
226
+ flex-shrink: 0;
227
+ color: var(--fg-muted);
228
+ transform-origin: 50% 50%;
229
+ transform: rotate(0deg);
230
+ transition: transform 140ms cubic-bezier(0.4, 0.0, 0.2, 1), color 140ms ease;
231
+ cursor: pointer;
232
+ vertical-align: middle;
233
+ }
234
+ .chevron.open { transform: rotate(90deg); }
235
+ .file-header:hover .chevron,
236
+ .hunk-header:hover .chevron,
237
+ .segment:hover .chevron { color: var(--fg); }
238
+ .file-path {
239
+ font-family: var(--mono); font-weight: 600;
240
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
241
+ }
242
+ .file-summary { color: var(--fg-muted); flex: 1; font-size: 13px; min-width: 0; }
243
+ .file-meta { display: flex; gap: 6px; font-size: 12px; color: var(--fg-muted); }
244
+ .file-meta .adds { color: var(--ok); }
245
+ .file-meta .dels { color: var(--err); }
246
+
247
+ .file-body { padding: 12px 14px; }
248
+ .file-overview {
249
+ font-size: 12px; color: var(--fg-muted);
250
+ padding: 6px 8px; background: var(--bg); border-radius: 4px; margin-bottom: 12px;
251
+ }
252
+ .file-overview .label { color: var(--fg-dim); margin-right: 4px; }
253
+
254
+ /* --- Hunks ---------------------------------------------------------------- */
255
+ .hunk {
256
+ margin: 12px 0; border: 1px solid var(--border); border-radius: 6px; overflow: hidden;
257
+ background: var(--bg);
258
+ }
259
+ .hunk.folded { border-left-width: 3px; }
260
+ .hunk-header {
261
+ display: grid;
262
+ grid-template-columns: auto 1fr auto;
263
+ grid-template-areas:
264
+ "chev pos meta"
265
+ ". intent intent";
266
+ column-gap: 10px;
267
+ row-gap: 4px;
268
+ padding: 8px 12px;
269
+ cursor: pointer;
270
+ background: var(--bg-panel); user-select: none;
271
+ }
272
+ .hunk-header > .chevron { grid-area: chev; align-self: start; margin-top: 2px; }
273
+ .hunk-pos {
274
+ grid-area: pos;
275
+ font-family: var(--mono); color: var(--fg-dim); font-size: 12px;
276
+ align-self: start;
277
+ }
278
+ .hunk-intent {
279
+ grid-area: intent;
280
+ color: var(--fg); font-size: 13px;
281
+ align-self: start;
282
+ }
283
+ .hunk-intent.empty { color: var(--fg-dim); font-style: italic; }
284
+ .hunk-intent.pending {
285
+ color: var(--fg-dim); font-style: italic;
286
+ animation: scr-pulse 1.4s ease-in-out infinite;
287
+ }
288
+ .hunk-intent.queued { color: var(--fg-dim); font-style: italic; opacity: 0.7; }
289
+ @keyframes scr-pulse {
290
+ 0%, 100% { opacity: 0.55; }
291
+ 50% { opacity: 1; }
292
+ }
293
+
294
+ /* Progress strip — shown only while augmentation is streaming. */
295
+ .scr-progress {
296
+ display: flex; flex-direction: column; gap: 4px;
297
+ padding: 6px 14px;
298
+ background: var(--bg-alt); border-bottom: 1px solid var(--border);
299
+ font-size: 12px; color: var(--fg-muted);
300
+ }
301
+ .scr-progress.hidden { display: none; }
302
+ .scr-progress-summary { display: flex; gap: 14px; align-items: center; flex-wrap: wrap; }
303
+ .scr-progress-overview {
304
+ display: inline-block; padding: 1px 8px; border-radius: 10px;
305
+ border: 1px solid var(--border); background: var(--bg);
306
+ }
307
+ .scr-progress-overview[data-state="pending"] { color: var(--fg-muted); }
308
+ .scr-progress-overview[data-state="running"] {
309
+ color: var(--accent); border-color: var(--accent);
310
+ animation: scr-pulse 1.4s ease-in-out infinite;
311
+ }
312
+ .scr-progress-overview[data-state="ok"] { color: var(--ok); border-color: var(--ok); }
313
+ .scr-progress-overview[data-state="failed"] { color: var(--err); border-color: var(--err); }
314
+ .scr-progress-hunks { color: var(--fg); font-weight: 500; }
315
+ .scr-progress-detail { color: var(--fg-dim); }
316
+ .scr-progress-grid {
317
+ display: flex; flex-wrap: wrap; gap: 3px;
318
+ padding-top: 2px;
319
+ }
320
+ .scr-progress-grid .sq {
321
+ width: 10px; height: 10px; border-radius: 2px;
322
+ background: var(--bg); border: 1px solid var(--border);
323
+ cursor: pointer;
324
+ }
325
+ .scr-progress-grid .sq[data-state="queued"] { background: var(--bg); }
326
+ .scr-progress-grid .sq[data-state="running"] {
327
+ background: var(--accent); border-color: var(--accent);
328
+ animation: scr-pulse 1.4s ease-in-out infinite;
329
+ }
330
+ .scr-progress-grid .sq[data-state="ok"] { background: var(--ok); border-color: var(--ok); }
331
+ .scr-progress-grid .sq[data-state="failed"] { background: var(--err); border-color: var(--err); }
332
+ .scr-progress-grid .sq:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
333
+ .hunk-meta { grid-area: meta; display: flex; gap: 6px; align-items: center; flex-shrink: 0; }
334
+ /* Subtle 3-star confidence indicator at the top-right of the hunk
335
+ * header. One filled star = low (the LLM hedged); three = high. The
336
+ * old `c=NN` chip was loud and numeric; stars read at a glance and
337
+ * stay out of the way. */
338
+ .hunk-confidence {
339
+ display: inline-flex;
340
+ gap: 1px;
341
+ font-size: 10px;
342
+ color: var(--fg-muted);
343
+ letter-spacing: 0.5px;
344
+ }
345
+ .hunk-confidence .conf-star.on { color: var(--fg-muted); }
346
+ .hunk-confidence[data-level="1"] .conf-star.on { color: var(--err); }
347
+ .hunk-confidence[data-level="1"] { opacity: 1; }
348
+
349
+ .context-icon {
350
+ font-size: 13px; color: var(--accent); cursor: help;
351
+ }
352
+
353
+ /* --- Segments ------------------------------------------------------------- */
354
+ .seg-list { padding: 8px 12px; background: var(--bg); }
355
+ .segment {
356
+ margin: 6px 0; padding: 6px 10px; background: var(--bg-alt);
357
+ border-left: 3px solid var(--border); border-radius: 3px;
358
+ display: flex; gap: 10px; align-items: flex-start; cursor: pointer;
359
+ }
360
+ .segment .chevron { margin-top: 1px; }
361
+ .segment-range {
362
+ font-family: var(--mono); color: var(--fg-dim); font-size: 12px; min-width: 90px;
363
+ flex-shrink: 0;
364
+ }
365
+ .segment-intent { flex: 1; font-size: 13px; }
366
+ .segment-intent.empty { color: var(--fg-dim); font-style: italic; }
367
+
368
+ .context-note, .refs {
369
+ padding: 6px 12px; background: var(--bg-alt); border-top: 1px solid var(--border);
370
+ font-size: 12px; color: var(--fg-muted);
371
+ }
372
+ .context-note { border-left: 3px solid var(--accent); padding-left: 16px; }
373
+ .refs a.ref-link {
374
+ display: inline-block; padding: 1px 6px; margin: 1px 4px 1px 0;
375
+ border: 1px solid var(--border); border-radius: 4px;
376
+ font-family: var(--mono); font-size: 11px;
377
+ color: var(--accent); text-decoration: none; background: var(--bg);
378
+ }
379
+ .refs a.ref-link:hover { background: var(--bg-panel); border-color: var(--accent); }
380
+ .refs .ref-reason { color: var(--fg-muted); font-size: 11px; }
381
+
382
+ /* --- Smells --------------------------------------------------------------- */
383
+ .smell {
384
+ display: inline-block; font-size: 11px; padding: 2px 8px;
385
+ border-radius: 10px; color: #fff; margin-left: 4px; cursor: help;
386
+ white-space: nowrap;
387
+ }
388
+ .smell.sev-info { background: #3b82f680; }
389
+ .smell.sev-minor { background: #64748b80; }
390
+ .smell.sev-major { background: #d2992280; }
391
+ .smell.sev-critical{ background: #f8514980; }
392
+
393
+ /* --- Diff grid -----------------------------------------------------------
394
+ * Two halves side by side, each its own horizontal-scroll container and
395
+ * its own 2-column grid (lineno + content). One shared scrollbar per
396
+ * side, zero mid-hunk scrollbars.
397
+ *
398
+ * Vertical alignment across halves: code rows use a uniform line-height
399
+ * and an enforced min-height on every cell, so every code row on both
400
+ * sides is exactly one line tall. Annotation rows break that symmetry
401
+ * (prose wraps); the JS inserts an invisible placeholder row on the
402
+ * opposite side and syncs its height to the real annotation via a
403
+ * ResizeObserver. Subgrid would do this for free but requires explicit
404
+ * outer row tracks, which we don't know at CSS compile time.
405
+ */
406
+ .diff {
407
+ display: grid;
408
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
409
+ font-family: var(--mono);
410
+ font-size: 12.5px;
411
+ line-height: 1.5;
412
+ border: 1px solid var(--border);
413
+ border-radius: 4px;
414
+ overflow: hidden;
415
+ background: var(--bg);
416
+ }
417
+ .diff .half {
418
+ display: grid;
419
+ /* `max-content` as the track *min* sizes the content column to the widest
420
+ * line, so every row's grid area — and thus its background tint — is as
421
+ * wide as the longest line and stays painted when a long sibling scrolls
422
+ * into view. `1fr` as the max lets the track grow to fill the column when
423
+ * the content is narrower than the viewport. A plain `minmax(0, 1fr)`
424
+ * floors the track at 0, so the widest line overflows its own area while
425
+ * shorter rows' tint stops at the viewport edge. */
426
+ grid-template-columns: 3.5em minmax(max-content, 1fr);
427
+ overflow-x: auto;
428
+ min-width: 0;
429
+ /* overflow-y is implicitly clipped (non-visible) when x is auto.
430
+ * The annotation arrow's upward bleed stays inside the same scroll
431
+ * container as its anchor row, so nothing should actually overflow
432
+ * vertically in practice. */
433
+ }
434
+
435
+ /* Each `.row` is a real grid box spanning both of `.half`'s columns,
436
+ * and uses `subgrid` so its two cells land in `.half`'s column tracks
437
+ * (matching widths across every row). Making the row an actual box
438
+ * rather than `display: contents` is required for `position: sticky`
439
+ * on `.cell-lineno` to work reliably in Chromium — sticky grandchildren
440
+ * of a display:contents parent have quirks that make them scroll along
441
+ * with content. */
442
+ .diff .row {
443
+ display: grid;
444
+ grid-template-columns: subgrid;
445
+ grid-column: 1 / -1;
446
+ }
447
+
448
+ .diff .cell {
449
+ padding: 0 8px;
450
+ white-space: pre;
451
+ min-width: 0;
452
+ /* Force one line-height per cell so empty cells on the opposite side
453
+ * of a pure-insert/delete row still occupy a full row, keeping the
454
+ * two halves vertically aligned line-for-line. */
455
+ min-height: calc(1.5em);
456
+ line-height: 1.5;
457
+ }
458
+ .diff .cell-lineno {
459
+ /* Pin line numbers to the left of their half so they stay visible
460
+ * when the content cells pan horizontally. z-index keeps them above
461
+ * the scrolling code; the background eliminates bleed-through. */
462
+ position: sticky;
463
+ left: 0;
464
+ z-index: 1;
465
+ color: var(--fg-dim);
466
+ text-align: right;
467
+ user-select: none;
468
+ background: var(--bg-alt);
469
+ border-right: 1px solid var(--border);
470
+ padding: 0 6px;
471
+ }
472
+ .diff .cell-lineno.empty { background: transparent; border-right-color: transparent; }
473
+ .diff .cell-content {
474
+ /* Width (and thus the row-kind background tint) comes from the content
475
+ * column track — `minmax(max-content, 1fr)` on `.half` above — so every
476
+ * cell stretches to the widest line. No per-cell `min-width` needed. */
477
+ /* Reserve a left gutter for the fold chevron so it can float outside
478
+ * the text flow without displacing the code. Every line — chevron or
479
+ * not — pays the same gutter so indentation reads consistently.
480
+ * `position: relative` makes the chevron's absolute positioning
481
+ * anchor here rather than on the scroll container. */
482
+ position: relative;
483
+ padding-left: 20px;
484
+ }
485
+ .diff .cell-content code { background: transparent !important; padding: 0 !important; }
486
+ .diff .cell-content.empty { background: transparent; min-width: 0; }
487
+
488
+ /* Per-row kind tints */
489
+ .diff .row-ctx .cell-content { background: var(--bg); }
490
+ .diff .row-ins .cell-content-new { background: rgba(63, 185, 80, 0.16); }
491
+ .diff .row-del .cell-content-old { background: rgba(248, 81, 73, 0.16); }
492
+ .diff .row-pair .cell-content-old { background: rgba(248, 81, 73, 0.16); }
493
+ .diff .row-pair .cell-content-new { background: rgba(63, 185, 80, 0.16); }
494
+
495
+ /* Intra-line sub-diff: the changed block within a replacement, over the
496
+ * light whole-line row tint. A subtle fill plus a coloured underline marks
497
+ * it. Underline (not a full ring) because a block that crosses highlight.js
498
+ * token spans is rendered as several adjacent `.char-chg` spans — a ring
499
+ * would draw a vertical edge at every join, breaking up joined text; an
500
+ * underline stays continuous across them. Applies to any changed row
501
+ * (pair, or a del/ins line inside a multi-line replacement block), so the
502
+ * side determines the colour. */
503
+ .diff .cell-content-old .char-chg {
504
+ background: rgba(248, 81, 73, 0.13);
505
+ border-bottom: 2px solid rgba(248, 81, 73, 0.7);
506
+ }
507
+ .diff .cell-content-new .char-chg {
508
+ background: rgba(63, 185, 80, 0.13);
509
+ border-bottom: 2px solid rgba(63, 185, 80, 0.7);
510
+ }
511
+
512
+ /* Symbol-focus search highlight: every occurrence of the focused symbol's
513
+ * name, painted search-style. Amber reads on both themes and over the
514
+ * green/red row + char-chg tints; the ring keeps it legible regardless. */
515
+ .diff .cell-content code .symbol-hit {
516
+ background: rgba(255, 196, 0, 0.4);
517
+ box-shadow: 0 0 0 1px rgba(255, 196, 0, 0.7);
518
+ border-radius: 2px;
519
+ }
520
+
521
+ /* Dim the empty placeholder on the opposite side of an ins/del */
522
+ .diff .row-ins .cell-content-old.empty,
523
+ .diff .row-del .cell-content-new.empty {
524
+ background: rgba(110, 118, 129, 0.05);
525
+ }
526
+
527
+ /* Indent-based fold control ---------------------------------------------- */
528
+ /* The chevron floats in the left gutter of .cell-content — it does NOT
529
+ * take text-flow space, so lines with chevrons read at the same indent
530
+ * as their siblings without. Without this, fold-header lines appeared
531
+ * ~2 chars more indented than their bodies and the nesting structure
532
+ * was visually illegible. */
533
+ .diff .fold-chev {
534
+ position: absolute;
535
+ left: 4px;
536
+ top: 2px;
537
+ width: 12px; height: 12px;
538
+ color: var(--fg-dim);
539
+ cursor: pointer;
540
+ outline: none;
541
+ }
542
+ .diff .fold-chev:hover { color: var(--accent); }
543
+ .diff .fold-chev:focus-visible {
544
+ outline: 1px dotted var(--accent);
545
+ outline-offset: 1px;
546
+ }
547
+
548
+ /* Annotation rows — emacs-flycheck-style boxed callouts linked to a line
549
+ * in the diff via an L-shaped arrow. The arrow emerges from the first
550
+ * non-whitespace column of the anchor line, drops down, and turns right
551
+ * into the box. Two-line height by default; box overflow clipped. */
552
+ .diff .cell-annotation {
553
+ /* An annotation lives in its half's grid and spans the two columns
554
+ * (gutter + content) of that half. Same rule on both sides — the
555
+ * annotation's parent determines which half it lands in. */
556
+ grid-column: 1 / -1;
557
+ display: flex;
558
+ align-items: flex-start;
559
+ gap: 6px;
560
+ padding: 0 8px 6px;
561
+ color: var(--fg-muted);
562
+ background: transparent;
563
+ overflow: visible; /* arrow top can extend above the cell */
564
+ }
565
+
566
+ /* Placeholder row on the opposite half, mirroring an annotation's
567
+ * height via JS-applied inline height so the two halves stay aligned
568
+ * even though only one side is populated. */
569
+ .diff .row-placeholder {
570
+ grid-column: 1 / -1;
571
+ visibility: hidden;
572
+ }
573
+ .diff .annot-arrow {
574
+ flex-shrink: 0;
575
+ /* margin-left + margin-top set inline by JS so the arrow's vertical
576
+ * segment lands at the character midpoint of the anchor line and the
577
+ * arrow's top reaches the line's vertical midline. */
578
+ color: var(--accent);
579
+ pointer-events: none;
580
+ }
581
+ .diff .annot-box {
582
+ flex: 0 1 auto;
583
+ padding: 4px 10px;
584
+ background: var(--bg-alt);
585
+ border: 1px solid var(--border);
586
+ border-left: 3px solid var(--accent);
587
+ border-radius: 4px;
588
+ font-size: 13px;
589
+ line-height: 1.4;
590
+ font-style: italic;
591
+ color: var(--fg);
592
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
593
+ font-weight: 400;
594
+ white-space: normal;
595
+ max-width: 64ch;
596
+ /* Fold summaries clamp to ~3 lines; line-notes and comments get to
597
+ * wrap freely. The annotations module applies the actual per-variant
598
+ * sizing inline via `layout.maxHeight`; this CSS default is only a
599
+ * pre-JS fallback. */
600
+ max-height: 5em;
601
+ overflow: hidden;
602
+ }
603
+ .diff .annot-box.missing {
604
+ color: var(--fg-dim);
605
+ border-left-color: var(--fg-dim);
606
+ }
607
+ .diff .annot-box.pending {
608
+ color: var(--fg-muted);
609
+ border-left-color: var(--accent);
610
+ font-style: italic;
611
+ animation: scr-pulse 1.4s ease-in-out infinite;
612
+ }
613
+ .diff .annot-box.failed {
614
+ color: var(--err);
615
+ border-left-color: var(--err);
616
+ cursor: pointer;
617
+ }
618
+
619
+ /* Reviewer comment variant (amber/warn border so it stands out from the
620
+ * blue LLM fold descriptions). */
621
+ .diff .annot-comment .annot-arrow { color: var(--warn); }
622
+ .diff .annot-comment .annot-box {
623
+ border-left-color: var(--warn);
624
+ font-style: normal;
625
+ max-height: none; /* comments can wrap freely */
626
+ }
627
+
628
+ /* Line-note variant: same palette as fold summaries, but the body is
629
+ * often several sentences and should not be clipped. */
630
+ .diff .annot-note .annot-box {
631
+ max-height: none;
632
+ }
633
+ .comment-body { white-space: pre-wrap; font-size: 100%; }
634
+
635
+ /* Header strip on ingested comments: avatar, @author, permalink. Pure
636
+ * local comments don't get a header (no `author`), so this only shows
637
+ * up on PR-sourced rows. */
638
+ .comment-header {
639
+ display: flex;
640
+ align-items: center;
641
+ gap: 6px;
642
+ margin-bottom: 4px;
643
+ font-size: 11px;
644
+ color: var(--fg-muted);
645
+ }
646
+ .comment-avatar {
647
+ width: 16px;
648
+ height: 16px;
649
+ border-radius: 50%;
650
+ display: block;
651
+ }
652
+ .comment-author { font-weight: 600; color: var(--fg); }
653
+ .comment-permalink {
654
+ margin-left: auto;
655
+ text-decoration: none;
656
+ color: var(--fg-muted);
657
+ }
658
+ .comment-permalink:hover { color: var(--accent); }
659
+
660
+ /* Anchor-status chip on the thread root. Surfaces when the original
661
+ * (commit_id, line) had to be propagated to head — "was line 42",
662
+ * "line removed since abc1234", etc. Compact + low-key so it doesn't
663
+ * compete with the author name. */
664
+ .comment-anchor-chip {
665
+ display: inline-flex;
666
+ align-items: center;
667
+ padding: 0 6px;
668
+ font-size: 10px;
669
+ text-transform: lowercase;
670
+ border-radius: 3px;
671
+ border: 1px solid var(--border);
672
+ color: var(--fg-muted);
673
+ background: var(--bg-panel);
674
+ }
675
+ .comment-anchor-chip.chip-shifted { border-color: var(--accent); color: var(--accent); }
676
+ .comment-anchor-chip.chip-orphaned,
677
+ .comment-anchor-chip.chip-file_gone {
678
+ border-color: var(--warn);
679
+ color: var(--warn);
680
+ }
681
+ .comment-anchor-chip.chip-commit_unavailable {
682
+ border-color: var(--err);
683
+ color: var(--err);
684
+ }
685
+
686
+ /* GitHub-rendered body: drop the pre-wrap white-space rule so block
687
+ * elements (<p>, <pre>, <ul>) lay out naturally. */
688
+ .comment-body.comment-body-html { white-space: normal; }
689
+ .comment-body.comment-body-html p { margin: 0 0 6px; }
690
+ .comment-body.comment-body-html p:last-child { margin-bottom: 0; }
691
+ .comment-body.comment-body-html pre {
692
+ background: var(--bg-panel);
693
+ border: 1px solid var(--border);
694
+ border-radius: 3px;
695
+ padding: 4px 6px;
696
+ overflow-x: auto;
697
+ font-size: 12px;
698
+ }
699
+ .comment-body.comment-body-html code {
700
+ font-family: var(--mono);
701
+ background: var(--bg-panel);
702
+ padding: 0 3px;
703
+ border-radius: 2px;
704
+ }
705
+ .comment-body.comment-body-html pre code {
706
+ background: transparent;
707
+ padding: 0;
708
+ }
709
+ .comment-body.comment-body-html blockquote {
710
+ margin: 0 0 6px;
711
+ padding-left: 8px;
712
+ border-left: 2px solid var(--border);
713
+ color: var(--fg-muted);
714
+ }
715
+ .comment-body.comment-body-html a { color: var(--accent); }
716
+ .comment-body.comment-body-html img { max-width: 100%; }
717
+
718
+ /* Thread block: all comments at one anchor share a single annotation
719
+ * box and arrow. Replies are indented inside that box rather than
720
+ * occupying their own annotation rows. */
721
+ .comment-thread { display: flex; flex-direction: column; gap: 8px; }
722
+ .comment-thread-entry {
723
+ /* Stacked entries get a divider between them so individual comments
724
+ * read as distinct without each needing its own box. */
725
+ border-top: 1px solid var(--border);
726
+ padding-top: 6px;
727
+ }
728
+ .comment-thread-entry:first-child {
729
+ border-top: none;
730
+ padding-top: 0;
731
+ }
732
+ /* Replies aren't visually indented — the thread is a flat stack and
733
+ * the divider between entries is enough. (The .comment-thread-reply
734
+ * class still attaches for any future treatment that wants to target
735
+ * replies specifically.) */
736
+ .comment-thread-actions {
737
+ display: flex;
738
+ justify-content: flex-end;
739
+ gap: 6px;
740
+ margin-top: 4px;
741
+ padding-top: 6px;
742
+ border-top: 1px solid var(--border);
743
+ }
744
+ .comment-btn-reply { padding: 2px 10px; }
745
+
746
+ /* LLM line-note annotation — the LLM observation plus a small
747
+ * "Add as comment" affordance. Clicking the button opens the comment
748
+ * editor pre-filled with the line-note text and the source annotation
749
+ * row is removed once a comment is saved. */
750
+ .line-note-body { display: flex; flex-direction: column; gap: 4px; }
751
+ .line-note-text { white-space: pre-wrap; }
752
+ .line-note-actions {
753
+ display: flex;
754
+ justify-content: flex-end;
755
+ margin-top: 2px;
756
+ }
757
+ .comment-btn-promote {
758
+ padding: 1px 8px;
759
+ font-size: 10px;
760
+ opacity: 0.7;
761
+ }
762
+ .comment-btn-promote:hover { opacity: 1; }
763
+
764
+ /* Smell pills that are click-to-promote get a subtle hover cue —
765
+ * pointer cursor + a hint underline. Non-promotable smells (the
766
+ * file-overview aggregator) stay static. */
767
+ .smell.smell-promotable { cursor: pointer; }
768
+ .smell.smell-promotable:hover {
769
+ outline: 1px solid currentColor;
770
+ outline-offset: -1px;
771
+ }
772
+
773
+ /* Resolved thread: collapsed header bar at the top of the thread block.
774
+ * The annot-box still surrounds the whole thread; the header is just
775
+ * the first child, with a softer tone so a wall of resolved threads
776
+ * doesn't shout. */
777
+ .diff .annot-comment-resolved .annot-box {
778
+ border-left-color: var(--fg-muted);
779
+ }
780
+ .comment-thread-resolved-header {
781
+ display: flex;
782
+ align-items: center;
783
+ gap: 8px;
784
+ background: transparent;
785
+ border: none;
786
+ color: var(--fg-muted);
787
+ font: inherit;
788
+ font-size: 12px;
789
+ padding: 2px 0;
790
+ cursor: pointer;
791
+ text-align: left;
792
+ }
793
+ .comment-thread-resolved-header:hover {
794
+ color: var(--fg);
795
+ }
796
+ .comment-thread-chev { width: 1em; display: inline-block; text-align: center; }
797
+ .comment-thread-resolved-tag {
798
+ color: var(--ok, #6cf09e);
799
+ font-weight: 600;
800
+ }
801
+ .comment-thread-resolved-meta { color: var(--fg-muted); }
802
+
803
+ /* The comment editor should not inherit the annot-box width/height
804
+ * clamps — it grows freely with the user's text. */
805
+ .diff .annot-editor .annot-box.comment-editor-box {
806
+ max-width: none;
807
+ max-height: none;
808
+ width: 100%;
809
+ }
810
+ .comment-editor-box textarea.comment-editor-input {
811
+ display: block;
812
+ width: 100%;
813
+ min-width: 32ch;
814
+ box-sizing: border-box;
815
+ background: var(--bg);
816
+ color: var(--fg);
817
+ border: 1px solid var(--border);
818
+ border-radius: 3px;
819
+ padding: 4px 6px;
820
+ font-family: var(--mono);
821
+ font-size: 13px;
822
+ /* Start at a single line; JS autosizes height on input. User can still
823
+ * drag the handle horizontally to widen if they want a wider box. */
824
+ resize: horizontal;
825
+ overflow: hidden;
826
+ outline: none;
827
+ }
828
+ .comment-editor-box textarea.comment-editor-input:focus {
829
+ border-color: var(--accent);
830
+ }
831
+ .comment-editor-bar, .comment-actions {
832
+ display: flex;
833
+ gap: 6px;
834
+ margin-top: 6px;
835
+ justify-content: flex-end;
836
+ }
837
+ .comment-btn {
838
+ background: var(--bg-panel);
839
+ color: var(--fg-muted);
840
+ border: 1px solid var(--border);
841
+ border-radius: 3px;
842
+ padding: 2px 8px;
843
+ font-size: 11px;
844
+ cursor: pointer;
845
+ font-family: inherit;
846
+ }
847
+ .comment-btn:hover { color: var(--fg); border-color: var(--accent); }
848
+ .comment-btn-save { background: var(--warn); color: #000; border-color: var(--warn); }
849
+ .comment-btn-save:hover { color: #000; background: #e0ab2c; }
850
+ .comment-btn-del:hover { color: var(--err); border-color: var(--err); }
851
+
852
+ /* Gutter hover affordance — show a faint + in the line-number column on
853
+ * hover so reviewers know the number is clickable. */
854
+ .diff .cell-lineno:not(.empty) { cursor: pointer; position: relative; }
855
+ .diff .cell-lineno:not(.empty):hover {
856
+ background: var(--bg-panel);
857
+ color: var(--accent);
858
+ }
859
+ .diff .cell-lineno:not(.empty):hover::before {
860
+ content: "+";
861
+ position: absolute;
862
+ left: 2px;
863
+ top: 50%;
864
+ transform: translateY(-50%);
865
+ font-size: 10px;
866
+ color: var(--warn);
867
+ }
868
+
869
+ /* "Done" button in the PR bar (only present when a session endpoint
870
+ * was baked into the HTML). */
871
+ .done-btn {
872
+ background: var(--warn);
873
+ color: #000;
874
+ border: 1px solid var(--warn);
875
+ border-radius: 6px;
876
+ padding: 4px 12px;
877
+ font-size: 12px;
878
+ font-weight: 600;
879
+ cursor: pointer;
880
+ margin-left: 8px;
881
+ }
882
+ .done-btn:hover:not(:disabled) { background: #e0ab2c; }
883
+ .done-btn:disabled { opacity: 0.7; cursor: default; }
884
+
885
+ @media (prefers-color-scheme: light) {
886
+ .diff .row-ins .cell-content-new { background: rgba(26, 127, 55, 0.10); }
887
+ .diff .row-del .cell-content-old { background: rgba(207, 34, 46, 0.10); }
888
+ .diff .row-pair .cell-content-old { background: rgba(207, 34, 46, 0.10); }
889
+ .diff .row-pair .cell-content-new { background: rgba(26, 127, 55, 0.10); }
890
+ }
891
+
892
+ /* --- Gap expansion chips (inter-hunk context) ---------------------------- */
893
+ .gap-chip {
894
+ margin: 8px 0;
895
+ padding: 6px 12px;
896
+ background: var(--bg-alt);
897
+ border: 1px dashed var(--border);
898
+ border-radius: 4px;
899
+ color: var(--fg-muted);
900
+ font-size: 12px;
901
+ cursor: pointer;
902
+ user-select: none;
903
+ text-align: center;
904
+ font-family: var(--mono);
905
+ transition: background 0.1s, color 0.1s, border-color 0.1s;
906
+ }
907
+ .gap-chip:hover {
908
+ background: var(--bg-panel);
909
+ color: var(--fg);
910
+ border-color: var(--accent);
911
+ border-style: solid;
912
+ }
913
+ .gap-chip .gap-icon { margin-right: 6px; color: var(--accent); }
914
+
915
+ .gap-expansion { position: relative; margin: 8px 0; }
916
+ .gap-expansion .diff { border-style: dashed; opacity: 0.92; }
917
+ .gap-expansion .gap-collapse {
918
+ position: absolute; top: 4px; right: 4px; z-index: 2;
919
+ background: var(--bg-panel); color: var(--fg-muted);
920
+ border: 1px solid var(--border); border-radius: 4px;
921
+ padding: 2px 8px; font-size: 11px; cursor: pointer;
922
+ font-family: var(--mono);
923
+ }
924
+ .gap-expansion .gap-collapse:hover { color: var(--fg); border-color: var(--accent); }
925
+
926
+ /* --- Status bar ----------------------------------------------------------- */
927
+ /* `position: sticky; bottom: 0` only pins relative to a scrolling
928
+ * ancestor, not the viewport — for a body-level footer it just sits
929
+ * at the end of the document flow. Use `fixed` to anchor it to the
930
+ * viewport bottom regardless of scroll position, and reserve space
931
+ * with body padding so the last content row isn't hidden behind it. */
932
+ .status-bar {
933
+ position: fixed; bottom: 0; left: 0; right: 0;
934
+ background: var(--bg-alt); border-top: 1px solid var(--border);
935
+ padding: 6px 16px; color: var(--fg-muted); font-size: 12px;
936
+ display: flex; gap: 16px; align-items: flex-end;
937
+ z-index: 40;
938
+ }
939
+ body { padding-bottom: 30px; }
940
+
941
+ /* The status counts sit on the right; when the console mounts, the
942
+ * prompt input takes the flexible left and pushes them over. */
943
+ .status-counts { margin-left: auto; white-space: nowrap; }
944
+
945
+ /* --- Review console ------------------------------------------------------- */
946
+ .console-input {
947
+ flex: 1 1 auto; min-width: 0;
948
+ resize: none; overflow-y: auto;
949
+ font: inherit; line-height: 1.4;
950
+ color: var(--fg); background: var(--bg-panel);
951
+ border: 1px solid var(--border); border-radius: 6px;
952
+ padding: 3px 8px;
953
+ }
954
+ .console-input::placeholder { color: var(--fg-muted); }
955
+ .console-input:focus { outline: none; border-color: var(--fg-muted); }
956
+ .console-input.busy { opacity: 0.7; }
957
+
958
+ /* Selection chip: the reviewer's pinned selection (Slice 4). Sits at the
959
+ * far left of the footer; visible only while a selection is pinned. */
960
+ .console-chip {
961
+ flex: 0 1 auto; min-width: 0;
962
+ display: inline-flex; align-items: center; gap: 4px;
963
+ max-width: 40%;
964
+ font-size: 12px; line-height: 1.2;
965
+ color: var(--fg); background: var(--bg-panel);
966
+ border: 1px solid var(--border); border-radius: 6px;
967
+ padding: 2px 4px 2px 8px;
968
+ }
969
+ .console-chip.hidden { display: none; }
970
+ .console-chip-label {
971
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
972
+ }
973
+ .console-chip-clear {
974
+ flex: 0 0 auto;
975
+ font: inherit; line-height: 1; color: var(--fg-muted);
976
+ background: none; border: none; cursor: pointer; padding: 0 2px;
977
+ }
978
+ .console-chip-clear:hover { color: var(--fg); }
979
+
980
+ /* Stop affordance: visible only while a turn streams. */
981
+ .console-stop {
982
+ flex: 0 0 auto; align-self: center;
983
+ font: inherit; font-size: 12px; line-height: 1.2;
984
+ color: var(--fg); background: var(--bg-panel);
985
+ border: 1px solid var(--border); border-radius: 6px;
986
+ padding: 3px 10px; cursor: pointer;
987
+ }
988
+ .console-stop:hover { border-color: var(--fg-muted); }
989
+ .console-stop:disabled { opacity: 0.5; cursor: default; }
990
+ .console-stop.hidden { display: none; }
991
+
992
+ /* Transcript drawer: anchored above the footer, grows upward to a cap
993
+ * then scrolls. `bottom` clears the fixed footer's reserved strip. */
994
+ .console-drawer {
995
+ position: fixed; left: 0; right: 0; bottom: 30px;
996
+ max-height: 58vh; overflow-y: auto;
997
+ background: var(--bg-alt); border-top: 1px solid var(--border);
998
+ z-index: 39;
999
+ padding: 12px 16px;
1000
+ }
1001
+ .console-drawer.hidden { display: none; }
1002
+ .console-transcript {
1003
+ display: flex; flex-direction: column; gap: 16px;
1004
+ /* Left-aligned (not centred) so the transcript lines up under the
1005
+ * prompt input, the way a terminal/Claude-Code log reads. The
1006
+ * max-width just caps line length for readability. */
1007
+ max-width: 980px; margin: 0;
1008
+ }
1009
+ /* Both question and answer read top-to-bottom, left-aligned and
1010
+ * full-width — no chat bubbles. The question is a quieter left-bordered
1011
+ * echo of the prompt; the answer below it is the prominent prose. */
1012
+ .console-q {
1013
+ max-width: 100%;
1014
+ white-space: pre-wrap; word-break: break-word;
1015
+ font-size: 13px; color: var(--fg-muted);
1016
+ border-left: 2px solid var(--border); padding-left: 10px;
1017
+ }
1018
+ .console-a {
1019
+ max-width: 100%;
1020
+ white-space: pre-wrap; word-break: break-word;
1021
+ font-size: 13px; color: var(--fg); line-height: 1.5;
1022
+ /* Pull the answer up under its question so a turn reads as one
1023
+ * block, with the larger transcript gap separating turns. */
1024
+ margin-top: -8px;
1025
+ }
1026
+ .console-a.console-pending .console-text { color: var(--fg-muted); }
1027
+ .console-a.console-error .console-text { color: var(--danger, #e5534b); }
1028
+ /* A cancelled turn keeps its partial text but dims it. */
1029
+ .console-a.console-cancelled .console-text { opacity: 0.65; }
1030
+
1031
+ /* Tool-activity lines stream above the answer text while the agent
1032
+ * reaches for tools; muted + monospaced to read as machine activity. */
1033
+ .console-activity {
1034
+ display: flex; flex-direction: column; gap: 2px;
1035
+ margin-bottom: 4px;
1036
+ }
1037
+ .console-activity:empty { display: none; }
1038
+ .console-tool {
1039
+ font-family: var(--mono, ui-monospace, monospace);
1040
+ font-size: 11px; color: var(--fg-muted);
1041
+ }
1042
+
1043
+ /* --- Rendered markdown answers (Slice 3) ---------------------------------
1044
+ * `.console-text` holds markdown-it output (sanitised); it manages its
1045
+ * own block whitespace, so override the pre-wrap inherited from the
1046
+ * plain-text era. Code fences keep their own `pre` whitespace. */
1047
+ .console-text { white-space: normal; }
1048
+ .console-text > :first-child { margin-top: 0; }
1049
+ .console-text > :last-child { margin-bottom: 0; }
1050
+ .console-text p { margin: 0 0 8px; }
1051
+ .console-text h1, .console-text h2, .console-text h3,
1052
+ .console-text h4, .console-text h5, .console-text h6 {
1053
+ margin: 14px 0 6px; line-height: 1.3; font-weight: 600;
1054
+ }
1055
+ .console-text h1 { font-size: 18px; }
1056
+ .console-text h2 { font-size: 16px; }
1057
+ .console-text h3 { font-size: 14px; }
1058
+ .console-text h4, .console-text h5, .console-text h6 { font-size: 13px; }
1059
+ .console-text ul, .console-text ol { margin: 0 0 8px; padding-left: 22px; }
1060
+ .console-text li { margin: 2px 0; }
1061
+ .console-text a { color: var(--accent); text-decoration: none; }
1062
+ .console-text a:hover { text-decoration: underline; }
1063
+ .console-text code {
1064
+ font-family: var(--mono, ui-monospace, monospace);
1065
+ font-size: 0.92em;
1066
+ background: var(--bg-panel); border-radius: 4px;
1067
+ padding: 0.1em 0.35em;
1068
+ }
1069
+ .console-text pre {
1070
+ background: var(--bg-alt); border: 1px solid var(--border);
1071
+ border-radius: 6px; padding: 10px 12px; margin: 0 0 8px;
1072
+ overflow-x: auto;
1073
+ }
1074
+ .console-text pre code {
1075
+ background: none; padding: 0; font-size: 12px; line-height: 1.45;
1076
+ }
1077
+ .console-text blockquote {
1078
+ margin: 0 0 8px; padding: 0 12px;
1079
+ border-left: 3px solid var(--border); color: var(--fg-muted);
1080
+ }
1081
+ .console-text table {
1082
+ border-collapse: collapse; margin: 0 0 8px; font-size: 12px;
1083
+ }
1084
+ .console-text th, .console-text td {
1085
+ border: 1px solid var(--border); padding: 4px 8px;
1086
+ }
1087
+ .console-text hr {
1088
+ border: none; border-top: 1px solid var(--border); margin: 12px 0;
1089
+ }
1090
+ /* A rendered mermaid diagram (its source `pre` is swapped out for this).
1091
+ * Centred, with the SVG scaled to fit the transcript width. */
1092
+ .console-mermaid {
1093
+ margin: 0 0 8px; text-align: center;
1094
+ }
1095
+ .console-mermaid svg { max-width: 100%; height: auto; }
1096
+
1097
+ /* --- Help overlay --------------------------------------------------------- */
1098
+ .help-overlay {
1099
+ position: fixed; inset: 0; background: rgba(0,0,0,0.6); z-index: 100;
1100
+ display: flex; align-items: center; justify-content: center;
1101
+ }
1102
+ .help-overlay.hidden { display: none; }
1103
+ .help-card {
1104
+ background: var(--bg-alt); border: 1px solid var(--border); border-radius: 8px;
1105
+ padding: 24px; max-width: 480px; color: var(--fg);
1106
+ }
1107
+ .help-card h3 { margin-top: 0; }
1108
+ .help-card kbd {
1109
+ display: inline-block; padding: 1px 6px; background: var(--bg-panel);
1110
+ border: 1px solid var(--border); border-radius: 4px; font-family: var(--mono);
1111
+ font-size: 11px; margin: 0 2px;
1112
+ }
1113
+
1114
+ /* --- Post-confirm modal (scr pr only) ------------------------------------ */
1115
+ /* Shown when the reviewer clicks Done in posting mode. Each row is a
1116
+ * comment that would be posted; per-row checkbox + delete give the
1117
+ * reviewer one last pass before the post fires. Reuses the help
1118
+ * overlay's backdrop pattern (fixed full-screen, centred card). */
1119
+ .post-modal {
1120
+ position: fixed; inset: 0; background: rgba(0,0,0,0.6); z-index: 110;
1121
+ display: flex; align-items: center; justify-content: center;
1122
+ }
1123
+ .post-modal.hidden { display: none; }
1124
+ .post-modal-card {
1125
+ background: var(--bg-alt); border: 1px solid var(--border); border-radius: 8px;
1126
+ padding: 20px 24px; width: min(720px, 90vw); max-height: 80vh;
1127
+ display: flex; flex-direction: column; color: var(--fg);
1128
+ }
1129
+ .post-modal-header { margin: 0 0 6px; font-size: 16px; }
1130
+ .post-modal-meta {
1131
+ margin: 0 0 14px; font-size: 12px; color: var(--fg-muted);
1132
+ font-family: var(--mono);
1133
+ }
1134
+ .post-modal-list {
1135
+ list-style: none; padding: 0; margin: 0; overflow-y: auto;
1136
+ border-top: 1px solid var(--border); border-bottom: 1px solid var(--border);
1137
+ max-height: 50vh;
1138
+ }
1139
+ .post-modal-empty {
1140
+ padding: 20px; text-align: center; color: var(--fg-muted); font-style: italic;
1141
+ }
1142
+ .post-row {
1143
+ display: flex; align-items: flex-start; gap: 8px;
1144
+ padding: 10px 0; border-bottom: 1px solid var(--border);
1145
+ }
1146
+ .post-row:last-child { border-bottom: 0; }
1147
+ .post-row-label {
1148
+ flex: 1; display: grid;
1149
+ grid-template-columns: auto 1fr auto; gap: 4px 10px;
1150
+ align-items: baseline; cursor: pointer;
1151
+ }
1152
+ .post-row-check { grid-row: 1 / span 2; margin-top: 2px; }
1153
+ .post-row-loc {
1154
+ font-family: var(--mono); font-size: 12px; color: var(--fg-muted);
1155
+ grid-column: 2; grid-row: 1;
1156
+ }
1157
+ .post-row-kind {
1158
+ font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.06em;
1159
+ color: var(--accent); grid-column: 3; grid-row: 1;
1160
+ }
1161
+ .post-row-body {
1162
+ font-size: 13px; white-space: pre-wrap; word-break: break-word;
1163
+ grid-column: 2 / span 2; grid-row: 2;
1164
+ }
1165
+ .post-row-delete {
1166
+ background: transparent; border: 1px solid var(--border); border-radius: 4px;
1167
+ width: 22px; height: 22px; padding: 0; line-height: 1;
1168
+ color: var(--fg-muted); cursor: pointer; font-size: 16px;
1169
+ }
1170
+ .post-row-delete:hover:not(:disabled) {
1171
+ color: var(--warn); border-color: var(--warn);
1172
+ }
1173
+ .post-row-delete:disabled { opacity: 0.5; cursor: default; }
1174
+ .post-modal-status {
1175
+ margin: 10px 0 0; font-size: 12px; color: var(--warn); min-height: 1.2em;
1176
+ }
1177
+ .post-modal-footer {
1178
+ display: flex; gap: 10px; justify-content: flex-end; margin-top: 14px;
1179
+ }
1180
+ .post-modal-footer button {
1181
+ background: var(--bg-panel); color: var(--fg);
1182
+ border: 1px solid var(--border); border-radius: 6px;
1183
+ padding: 6px 14px; font-size: 13px; cursor: pointer;
1184
+ }
1185
+ .post-modal-footer .post-confirm {
1186
+ background: var(--accent); border-color: var(--accent); color: #000; font-weight: 600;
1187
+ }
1188
+ .post-modal-footer .post-confirm:hover:not(:disabled) { filter: brightness(1.1); }
1189
+ .post-modal-footer button:disabled { opacity: 0.6; cursor: default; }
1190
+ .post-modal-success {
1191
+ display: flex; gap: 12px; align-items: flex-start;
1192
+ padding: 16px 0; margin-bottom: 4px;
1193
+ }
1194
+ .post-modal-tick {
1195
+ font-size: 24px; color: var(--ok, #2ea043); line-height: 1;
1196
+ }
1197
+ .post-modal-success-text { font-size: 14px; }
1198
+ .post-modal-link {
1199
+ display: inline-block; margin-top: 4px;
1200
+ color: var(--accent); text-decoration: none;
1201
+ }
1202
+ .post-modal-link:hover { text-decoration: underline; }