@diegopetrucci/pi-annotate-git-diff 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/web/index.html ADDED
@@ -0,0 +1,913 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="h-full bg-[#0d1117]">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>annotate-git-diff</title>
7
+ <script>
8
+ __INLINE_TAILWIND_JS__
9
+ </script>
10
+ <style>
11
+ :root {
12
+ --color-review-bg: #0d1117;
13
+ --color-review-inset: #010409;
14
+ --color-review-panel: #151b23;
15
+ --color-review-panel-hover: #1f242c;
16
+ --color-review-border: #3d444d;
17
+ --color-review-border-muted: #2f353d;
18
+ --color-review-text: #f0f6fc;
19
+ --color-review-muted: #9198a1;
20
+ --color-review-subtle: #6e7681;
21
+ --color-review-accent: #4493f8;
22
+ --color-review-accent-emphasis: #1f6feb;
23
+ --color-review-success: #3fb950;
24
+ --color-review-success-emphasis: #238636;
25
+ --color-review-danger: #f85149;
26
+ --color-review-attention: #d29922;
27
+ }
28
+
29
+ .bg-review-bg { background-color: var(--color-review-bg); }
30
+ .bg-review-panel { background-color: var(--color-review-panel); }
31
+ .border-review-border { border-color: var(--color-review-border); }
32
+ .text-review-text { color: var(--color-review-text); }
33
+ .text-review-muted { color: var(--color-review-muted); }
34
+ .placeholder\:text-review-muted::placeholder { color: var(--color-review-muted); }
35
+ .focus\:border-review-accent:focus { border-color: var(--color-review-accent); }
36
+ .focus\:ring-review-accent:focus { --tw-ring-color: var(--color-review-accent); }
37
+ </style>
38
+ <style>
39
+ html, body {
40
+ width: 100%;
41
+ height: 100%;
42
+ overflow: hidden;
43
+ }
44
+
45
+ .scrollbar-thin::-webkit-scrollbar {
46
+ width: 10px;
47
+ height: 10px;
48
+ }
49
+
50
+ .scrollbar-thin::-webkit-scrollbar-thumb {
51
+ background: #3d444d;
52
+ border-radius: 999px;
53
+ border: 3px solid #151b23;
54
+ }
55
+
56
+ .scrollbar-thin::-webkit-scrollbar-track {
57
+ background: transparent;
58
+ }
59
+
60
+ .review-popover {
61
+ position: absolute;
62
+ width: min(420px, 92vw);
63
+ background: #151b23;
64
+ border: 1px solid #3d444d;
65
+ border-radius: 10px;
66
+ box-shadow: 0 8px 24px rgba(1, 4, 9, 0.6);
67
+ padding: 12px;
68
+ z-index: 100;
69
+ animation: reviewPopIn 120ms ease-out;
70
+ }
71
+
72
+ @keyframes reviewPopIn {
73
+ from { opacity: 0; transform: translateY(-4px); }
74
+ to { opacity: 1; transform: none; }
75
+ }
76
+
77
+ .review-popover::before {
78
+ content: "";
79
+ position: absolute;
80
+ top: -6px;
81
+ width: 10px;
82
+ height: 10px;
83
+ background: #151b23;
84
+ border-top: 1px solid #3d444d;
85
+ border-left: 1px solid #3d444d;
86
+ transform: rotate(45deg);
87
+ }
88
+
89
+ .review-popover[data-arrow="right"]::before { right: 18px; }
90
+ .review-popover[data-arrow="left"]::before { left: 18px; }
91
+
92
+ .review-popover textarea {
93
+ min-height: 120px;
94
+ max-height: 40vh;
95
+ }
96
+
97
+ /* Icon button — compact square toggle matching the GitHub Dark palette. */
98
+ .icon-button {
99
+ display: inline-flex;
100
+ align-items: center;
101
+ justify-content: center;
102
+ width: 28px;
103
+ height: 28px;
104
+ padding: 0;
105
+ border-radius: 6px;
106
+ border: 1px solid #3d444d;
107
+ background: #151b23;
108
+ color: #9198a1;
109
+ cursor: pointer;
110
+ transition: background-color 120ms, color 120ms, border-color 120ms;
111
+ flex-shrink: 0;
112
+ }
113
+
114
+ .icon-button:hover:not([disabled]) {
115
+ background: #1f242c;
116
+ color: #f0f6fc;
117
+ }
118
+
119
+ .icon-button[data-active="true"] {
120
+ background: rgba(35, 134, 54, 0.15);
121
+ border-color: rgba(63, 185, 80, 0.45);
122
+ color: #3fb950;
123
+ }
124
+
125
+ .icon-button[data-active="true"]:hover:not([disabled]) {
126
+ background: rgba(35, 134, 54, 0.25);
127
+ color: #3fb950;
128
+ }
129
+
130
+ .icon-button[disabled] {
131
+ opacity: 0.4;
132
+ cursor: default;
133
+ }
134
+
135
+ .icon-button svg {
136
+ width: 14px;
137
+ height: 14px;
138
+ }
139
+
140
+ /* Added-only files in inline diff mode: suppress the original gutter/line-number column
141
+ that Monaco still paints alongside the modified line numbers.
142
+ Keep the original editor's layout width intact: Monaco uses that width for word-wrap
143
+ height calculation, and collapsing it to 0px makes wrapped added files render huge
144
+ folded/diagonal gaps. */
145
+ #editor-container[data-added-only="true"] .monaco-diff-editor .editor.original,
146
+ #editor-container[data-added-only="true"] .monaco-diff-editor .original-in-monaco {
147
+ visibility: hidden;
148
+ }
149
+ #editor-container[data-added-only="true"] .monaco-editor .line-numbers.original,
150
+ #editor-container[data-added-only="true"] .monaco-editor .margin-view-overlays .line-numbers[data-lineNumberType="original"] {
151
+ display: none;
152
+ }
153
+
154
+ /* === GitHub-style sidebar === */
155
+ .gh-tree-row {
156
+ display: flex;
157
+ align-items: center;
158
+ gap: 6px;
159
+ width: 100%;
160
+ padding: 3px 8px 3px 8px;
161
+ text-align: left;
162
+ font-size: 13px;
163
+ color: #c9d1d9;
164
+ border-radius: 6px;
165
+ cursor: pointer;
166
+ line-height: 1.4;
167
+ }
168
+ .gh-tree-row:hover { background: #1f242c; color: #f0f6fc; }
169
+ .gh-tree-row[data-selected="true"] { background: rgba(56,139,253,0.15); color: #f0f6fc; }
170
+ .gh-tree-row .gh-row-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
171
+ .gh-tree-row .gh-row-trail { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
172
+ .gh-tree-row svg { flex-shrink: 0; }
173
+ .gh-dir-icon { color: #79c0ff; }
174
+ .gh-file-icon { color: #9198a1; }
175
+
176
+ /* GitHub-style status square (A / M / D / R). */
177
+ .gh-status {
178
+ display: inline-flex;
179
+ align-items: center;
180
+ justify-content: center;
181
+ width: 16px;
182
+ height: 16px;
183
+ border-radius: 4px;
184
+ font-size: 9px;
185
+ font-weight: 700;
186
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
187
+ line-height: 1;
188
+ flex-shrink: 0;
189
+ }
190
+ .gh-status-added { background: rgba(46,160,67,0.35); color: #3fb950; }
191
+ .gh-status-modified { background: rgba(210,153,34,0.30); color: #d29922; }
192
+ .gh-status-deleted { background: rgba(248,81,73,0.30); color: #f85149; }
193
+ .gh-status-renamed { background: rgba(56,139,253,0.30); color: #58a6ff; }
194
+
195
+ .gh-comment-count {
196
+ display: inline-flex;
197
+ align-items: center;
198
+ justify-content: center;
199
+ min-width: 18px;
200
+ height: 16px;
201
+ padding: 0 4px;
202
+ border-radius: 9999px;
203
+ background: #1f242c;
204
+ color: #f0f6fc;
205
+ font-size: 10px;
206
+ font-weight: 600;
207
+ border: 1px solid #3d444d;
208
+ }
209
+
210
+ /* === GitHub-style file header (main pane) === */
211
+ .gh-file-header {
212
+ display: flex;
213
+ align-items: center;
214
+ gap: 8px;
215
+ min-width: 0;
216
+ }
217
+ .gh-file-path {
218
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
219
+ font-size: 13px;
220
+ font-weight: 600;
221
+ color: #f0f6fc;
222
+ overflow: hidden;
223
+ text-overflow: ellipsis;
224
+ white-space: nowrap;
225
+ }
226
+ .gh-file-path .gh-file-path-dir { color: #9198a1; font-weight: 400; }
227
+
228
+ .gh-diff-stats {
229
+ display: inline-flex;
230
+ align-items: center;
231
+ gap: 6px;
232
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
233
+ font-size: 11px;
234
+ color: #9198a1;
235
+ }
236
+ .gh-diff-stats-plus { color: #3fb950; }
237
+ .gh-diff-stats-minus { color: #f85149; }
238
+
239
+ /* === GitHub-style Viewed checkbox (replaces the Reviewed icon toggle) === */
240
+ .gh-viewed {
241
+ display: inline-flex;
242
+ align-items: center;
243
+ gap: 6px;
244
+ padding: 4px 10px;
245
+ border-radius: 6px;
246
+ border: 1px solid #3d444d;
247
+ background: #151b23;
248
+ color: #c9d1d9;
249
+ font-size: 12px;
250
+ font-weight: 500;
251
+ cursor: pointer;
252
+ user-select: none;
253
+ transition: background-color 120ms, color 120ms, border-color 120ms;
254
+ }
255
+ .gh-viewed:hover:not([disabled]) { background: #1f242c; color: #f0f6fc; }
256
+ .gh-viewed[data-active="true"] {
257
+ background: rgba(35,134,54,0.15);
258
+ border-color: rgba(63,185,80,0.45);
259
+ color: #3fb950;
260
+ }
261
+ .gh-viewed .gh-viewed-box {
262
+ display: inline-flex;
263
+ align-items: center;
264
+ justify-content: center;
265
+ width: 14px;
266
+ height: 14px;
267
+ border-radius: 3px;
268
+ border: 1px solid #6e7681;
269
+ background: transparent;
270
+ }
271
+ .gh-viewed[data-active="true"] .gh-viewed-box {
272
+ border-color: #3fb950;
273
+ background: #238636;
274
+ }
275
+ .gh-viewed .gh-viewed-box svg { width: 10px; height: 10px; opacity: 0; color: #fff; }
276
+ .gh-viewed[data-active="true"] .gh-viewed-box svg { opacity: 1; }
277
+
278
+ /* === Primary CTA (submit review) === */
279
+ .gh-primary-btn {
280
+ display: inline-flex;
281
+ align-items: center;
282
+ gap: 4px;
283
+ padding: 6px 12px;
284
+ border-radius: 6px;
285
+ border: 1px solid rgba(240,246,252,0.1);
286
+ background: #238636;
287
+ color: #ffffff;
288
+ font-size: 12px;
289
+ font-weight: 600;
290
+ cursor: pointer;
291
+ transition: background-color 120ms;
292
+ }
293
+ .gh-primary-btn:hover:not([disabled]) { background: #2ea043; }
294
+ .gh-primary-btn[disabled] { opacity: 0.5; cursor: default; }
295
+
296
+ /* === Commit picker rows === */
297
+ .commit-row {
298
+ display: flex;
299
+ align-items: flex-start;
300
+ gap: 8px;
301
+ width: 100%;
302
+ padding: 6px 8px;
303
+ text-align: left;
304
+ border-radius: 6px;
305
+ cursor: pointer;
306
+ color: #c9d1d9;
307
+ }
308
+ .commit-row:hover { background: #1f242c; }
309
+ .commit-row[data-selected="true"] {
310
+ background: rgba(56,139,253,0.15);
311
+ color: #f0f6fc;
312
+ }
313
+ .commit-row-sha {
314
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
315
+ font-size: 10.5px;
316
+ color: #79c0ff;
317
+ padding-top: 2px;
318
+ flex-shrink: 0;
319
+ }
320
+ .commit-row-body { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }
321
+ .commit-row-subject {
322
+ font-size: 12.5px;
323
+ font-weight: 500;
324
+ overflow: hidden;
325
+ text-overflow: ellipsis;
326
+ white-space: nowrap;
327
+ }
328
+ .commit-row-meta {
329
+ font-size: 10.5px;
330
+ color: #9198a1;
331
+ overflow: hidden;
332
+ text-overflow: ellipsis;
333
+ white-space: nowrap;
334
+ }
335
+ .commit-row-status {
336
+ font-size: 10px;
337
+ color: #9198a1;
338
+ padding-top: 2px;
339
+ flex-shrink: 0;
340
+ }
341
+
342
+ /* === GitHub-style Monaco diff backgrounds === */
343
+ .monaco-editor .line-insert,
344
+ .monaco-editor .char-insert {
345
+ background: rgba(46,160,67,0.18);
346
+ }
347
+ .monaco-editor .line-delete,
348
+ .monaco-editor .char-delete {
349
+ background: rgba(248,81,73,0.18);
350
+ }
351
+
352
+ #editor-container {
353
+ height: 100%;
354
+ min-height: 0;
355
+ overflow: hidden;
356
+ position: relative; /* anchor for overlays */
357
+ }
358
+
359
+ #diff-editor-host,
360
+ #single-editor-host {
361
+ position: absolute;
362
+ inset: 0;
363
+ z-index: 1;
364
+ height: 100%;
365
+ }
366
+
367
+ #single-editor-host {
368
+ display: none;
369
+ }
370
+
371
+ #editor-container[data-text-editor-mode="single"] #diff-editor-host {
372
+ display: none;
373
+ }
374
+
375
+ #editor-container[data-text-editor-mode="single"] #single-editor-host {
376
+ display: block;
377
+ }
378
+
379
+ #diff-editor-host .monaco-diff-editor,
380
+ #diff-editor-host .monaco-editor,
381
+ #single-editor-host .monaco-editor {
382
+ height: 100%;
383
+ }
384
+
385
+ #editor-container[data-preview-active="true"] #diff-editor-host,
386
+ #editor-container[data-preview-active="true"] #single-editor-host {
387
+ opacity: 0;
388
+ pointer-events: none;
389
+ }
390
+
391
+ #binary-preview {
392
+ position: absolute;
393
+ inset: 0;
394
+ z-index: 4;
395
+ display: none;
396
+ overflow: auto;
397
+ background: #0d1117;
398
+ padding: 20px;
399
+ }
400
+
401
+ #binary-preview[data-active="true"] {
402
+ display: block;
403
+ }
404
+
405
+ .binary-preview-grid {
406
+ display: grid;
407
+ gap: 16px;
408
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
409
+ }
410
+
411
+ .binary-preview-card {
412
+ border: 1px solid #3d444d;
413
+ border-radius: 10px;
414
+ background: #151b23;
415
+ overflow: hidden;
416
+ }
417
+
418
+ .binary-preview-card-header {
419
+ display: flex;
420
+ align-items: center;
421
+ justify-content: space-between;
422
+ gap: 12px;
423
+ padding: 10px 12px;
424
+ border-bottom: 1px solid #3d444d;
425
+ font-size: 12px;
426
+ color: #9198a1;
427
+ }
428
+
429
+ .binary-preview-card-body {
430
+ padding: 12px;
431
+ }
432
+
433
+ .binary-preview-image {
434
+ display: block;
435
+ width: 100%;
436
+ height: auto;
437
+ border-radius: 8px;
438
+ background: #010409;
439
+ }
440
+
441
+ .binary-preview-empty {
442
+ min-height: 180px;
443
+ display: flex;
444
+ align-items: center;
445
+ justify-content: center;
446
+ border: 1px dashed #3d444d;
447
+ border-radius: 8px;
448
+ color: #9198a1;
449
+ background: #0d1117;
450
+ text-align: center;
451
+ padding: 16px;
452
+ font-size: 13px;
453
+ }
454
+
455
+ .binary-preview-note {
456
+ margin-top: 16px;
457
+ padding: 12px 14px;
458
+ border: 1px solid #3d444d;
459
+ border-radius: 10px;
460
+ background: #151b23;
461
+ color: #9198a1;
462
+ font-size: 13px;
463
+ line-height: 1.5;
464
+ }
465
+
466
+ .gh-kind-badge {
467
+ display: inline-flex;
468
+ align-items: center;
469
+ justify-content: center;
470
+ min-width: 32px;
471
+ padding: 1px 6px;
472
+ border-radius: 999px;
473
+ border: 1px solid #3d444d;
474
+ background: #0d1117;
475
+ color: #9198a1;
476
+ font-size: 10px;
477
+ font-weight: 600;
478
+ letter-spacing: 0.02em;
479
+ }
480
+
481
+ /* Opaque cover painted on top of the diff editor during remount. Monaco keeps
482
+ rendering/computing underneath at full speed — unlike opacity:0 on the
483
+ container, Monaco is NOT affected by this overlay and reliably settles
484
+ hideUnchangedRegions before we unveil. Removing the cover is a single
485
+ discrete paint, so no sub-frame shift is observable. */
486
+
487
+ #editor-cover {
488
+ position: absolute;
489
+ inset: 0;
490
+ background: #0d1117;
491
+ z-index: 5;
492
+ pointer-events: none;
493
+ opacity: 0;
494
+ transition: opacity 80ms linear;
495
+ }
496
+
497
+ #editor-cover[data-active="true"] {
498
+ opacity: 1;
499
+ transition: none;
500
+ }
501
+
502
+ #editor-container[data-asset-failure="true"] #diff-editor-host,
503
+ #editor-container[data-asset-failure="true"] #single-editor-host,
504
+ #editor-container[data-asset-failure="true"] #binary-preview,
505
+ #editor-container[data-asset-failure="true"] #editor-cover {
506
+ display: none !important;
507
+ }
508
+
509
+ #asset-failure-panel {
510
+ position: absolute;
511
+ inset: 20px;
512
+ z-index: 7;
513
+ display: none;
514
+ align-items: center;
515
+ justify-content: center;
516
+ padding: 20px;
517
+ }
518
+
519
+ #editor-container[data-asset-failure="true"] #asset-failure-panel {
520
+ display: flex;
521
+ }
522
+
523
+ .asset-failure-card {
524
+ width: min(680px, 100%);
525
+ padding: 24px;
526
+ border: 1px solid #3d444d;
527
+ border-radius: 12px;
528
+ background: #151b23;
529
+ box-shadow: 0 16px 40px rgba(1, 4, 9, 0.45);
530
+ }
531
+
532
+ .asset-failure-eyebrow {
533
+ font-size: 11px;
534
+ font-weight: 700;
535
+ letter-spacing: 0.08em;
536
+ text-transform: uppercase;
537
+ color: #d29922;
538
+ }
539
+
540
+ .asset-failure-title {
541
+ margin: 10px 0 0;
542
+ font-size: 22px;
543
+ line-height: 1.2;
544
+ color: #f0f6fc;
545
+ }
546
+
547
+ .asset-failure-copy {
548
+ margin: 10px 0 0;
549
+ color: #c9d1d9;
550
+ font-size: 14px;
551
+ line-height: 1.6;
552
+ }
553
+
554
+ .asset-failure-detail {
555
+ margin: 14px 0 0;
556
+ padding: 12px 14px;
557
+ border: 1px solid #2f353d;
558
+ border-radius: 10px;
559
+ background: #0d1117;
560
+ color: #9198a1;
561
+ font-size: 12px;
562
+ line-height: 1.5;
563
+ white-space: pre-wrap;
564
+ word-break: break-word;
565
+ }
566
+
567
+ .asset-failure-detail[hidden] {
568
+ display: none;
569
+ }
570
+
571
+ .monaco-editor .view-zones {
572
+ z-index: 10;
573
+ }
574
+
575
+ .view-zone-container {
576
+ padding: 8px 16px 10px 16px;
577
+ border-top: 1px solid #3d444d;
578
+ border-bottom: 1px solid #3d444d;
579
+ background: #151b23;
580
+ overflow: visible;
581
+ }
582
+
583
+ .monaco-editor .review-comment-line-original {
584
+ background: rgba(210, 153, 34, 0.15);
585
+ }
586
+
587
+ .monaco-editor .review-comment-line-modified {
588
+ background: rgba(68, 147, 248, 0.15);
589
+ }
590
+
591
+ .monaco-editor .review-comment-glyph-original,
592
+ .monaco-editor .review-comment-glyph-modified {
593
+ border-radius: 999px;
594
+ width: 8px;
595
+ height: 8px;
596
+ margin-left: 6px;
597
+ margin-top: 5px;
598
+ cursor: pointer;
599
+ }
600
+
601
+ .monaco-editor .review-comment-glyph-original {
602
+ background: #d29922;
603
+ }
604
+
605
+ .monaco-editor .review-comment-glyph-modified {
606
+ background: #4493f8;
607
+ }
608
+
609
+ .monaco-editor .review-glyph-plus {
610
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='16' height='16'%3E%3Cpath fill='%23f0f6fc' d='M7.75 2a.75.75 0 0 1 .75.75V7h4.25a.75.75 0 0 1 0 1.5H8.5v4.25a.75.75 0 0 1-1.5 0V8.5H2.75a.75.75 0 0 1 0-1.5H7V2.75A.75.75 0 0 1 7.75 2Z'%3E%3C/path%3E%3C/svg%3E");
611
+ background-repeat: no-repeat;
612
+ background-position: center;
613
+ background-size: 12px;
614
+ background-color: #238636;
615
+ border-radius: 4px;
616
+ cursor: pointer;
617
+ margin-left: 2px;
618
+ margin-top: 2px;
619
+ width: 16px;
620
+ height: 16px;
621
+ box-shadow: 0 0 0 1px rgba(240, 246, 252, 0.08);
622
+ }
623
+ </style>
624
+ </head>
625
+ <body class="h-full overflow-hidden bg-review-bg text-review-text antialiased">
626
+ <div id="root-layout" class="flex h-full w-full flex-col">
627
+ <div class="flex items-center justify-between gap-4 border-b border-review-border bg-[#010409] px-4 py-3">
628
+ <div class="min-w-0 flex items-center gap-3">
629
+ <button type="button" id="toggle-sidebar-button" class="icon-button" title="Toggle sidebar" aria-label="Toggle sidebar">
630
+ <svg aria-hidden="true" focusable="false" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="12" height="10" rx="1.25"/><line x1="6" y1="3" x2="6" y2="13"/></svg>
631
+ </button>
632
+ <div class="min-w-0">
633
+ <div id="window-title" class="text-[15px] font-semibold text-white">Review</div>
634
+ <div id="repo-root" class="truncate text-[11px] text-review-muted"></div>
635
+ </div>
636
+ </div>
637
+ <div class="flex items-center gap-2">
638
+ <div id="summary" class="max-w-[24rem] truncate text-[11px] text-review-muted"></div>
639
+ <button type="button" id="refresh-review-button" class="cursor-pointer rounded-md border border-review-border bg-review-panel px-2.5 py-1 text-[11px] font-medium text-review-text hover:bg-[#1f242c]" title="Refresh review data" aria-label="Refresh review data" style="display:none;">Refresh</button>
640
+ <button type="button" id="overall-comment-button" class="icon-button" title="Overall review note" aria-label="Overall review note">
641
+ <svg aria-hidden="true" focusable="false" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M10.5 2.5l3 3-8 8H2.5V10.5z"/><path d="M9 4l3 3"/></svg>
642
+ </button>
643
+ <button type="button" id="cancel-button" class="cursor-pointer rounded-md border border-review-border bg-review-panel px-3 py-1.5 text-xs font-medium text-review-text hover:bg-red-500/10 hover:text-red-400 hover:border-red-500/30">Cancel</button>
644
+ <button type="button" id="submit-button" class="gh-primary-btn">
645
+ <svg aria-hidden="true" focusable="false" viewBox="0 0 16 16" width="12" height="12" fill="currentColor"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"/></svg>
646
+ Submit review
647
+ </button>
648
+ </div>
649
+ </div>
650
+
651
+ <div id="content-layout" class="flex min-h-0 flex-1">
652
+ <aside id="sidebar" class="flex min-h-0 shrink-0 flex-col border-r border-review-border bg-[#0d1117]" style="width: 280px; min-width: 280px; flex-basis: 280px; overflow: hidden;">
653
+ <div class="border-b border-review-border px-4 py-3">
654
+ <div class="mb-2 flex items-center justify-between gap-2">
655
+ <div id="sidebar-title" class="text-[11px] font-semibold uppercase tracking-wider text-review-muted">Files</div>
656
+ <div class="text-[10px] text-review-muted">Fuzzy filter</div>
657
+ </div>
658
+ <div class="mb-3 flex flex-wrap items-center gap-2">
659
+ <button type="button" id="scope-branch-button" class="cursor-pointer rounded-md border border-review-border bg-review-panel px-2.5 py-1 text-[11px] font-medium text-review-text hover:bg-[#1f242c]">Branch</button>
660
+ <button type="button" id="scope-commits-button" class="cursor-pointer rounded-md border border-review-border bg-review-panel px-2.5 py-1 text-[11px] font-medium text-review-text hover:bg-[#1f242c]">Commits</button>
661
+ <button type="button" id="scope-all-button" class="cursor-pointer rounded-md border border-review-border bg-review-panel px-2.5 py-1 text-[11px] font-medium text-review-text hover:bg-[#1f242c]">All</button>
662
+ </div>
663
+ <input id="sidebar-search-input" type="text" spellcheck="false" autocomplete="off" placeholder="Search files" class="w-full rounded-md border border-review-border bg-review-panel px-3 py-2 text-sm text-review-text outline-none placeholder:text-review-muted focus:border-review-accent focus:ring-1 focus:ring-review-accent">
664
+ </div>
665
+ <div id="commit-picker" class="border-b border-review-border" style="display:none;">
666
+ <div class="px-4 py-2 text-[10px] font-semibold uppercase tracking-wider text-review-muted">Commits</div>
667
+ <div id="commit-list" class="scrollbar-thin max-h-[38vh] overflow-auto px-2 pb-2"></div>
668
+ </div>
669
+ <div id="file-tree" class="scrollbar-thin min-h-0 flex-1 overflow-auto px-2 pb-2"></div>
670
+ </aside>
671
+
672
+ <main id="main-pane" class="flex min-h-0 min-w-0 flex-1 flex-col bg-[#0d1117]">
673
+ <div class="flex items-center justify-between gap-4 border-b border-review-border bg-[#0d1117] px-4 py-2">
674
+ <div class="gh-file-header min-w-0 flex-1">
675
+ <button type="button" id="file-collapse-button" class="icon-button" title="Collapse file" aria-label="Collapse file" style="width:22px;height:22px;">
676
+ <svg aria-hidden="true" focusable="false" viewBox="0 0 16 16" fill="currentColor"><path d="M12.78 6.22a.749.749 0 0 1 0 1.06l-4.25 4.25a.749.749 0 0 1-1.06 0L3.22 7.28a.749.749 0 1 1 1.06-1.06L8 9.939l3.72-3.719a.749.749 0 0 1 1.06 0Z"/></svg>
677
+ </button>
678
+ <span id="file-status-badge" class="gh-status" style="display:none;"></span>
679
+ <div class="min-w-0 flex-1">
680
+ <div id="current-file-label" class="gh-file-path"></div>
681
+ <div id="mode-hint" class="mt-0.5 text-[11px] text-review-muted"></div>
682
+ </div>
683
+ <div id="file-diff-stats" class="gh-diff-stats" style="display:none;"></div>
684
+ </div>
685
+ <div class="flex items-center gap-1.5">
686
+ <button type="button" id="toggle-wrap-button" class="icon-button" title="Wrap long lines" aria-label="Wrap lines">
687
+ <svg aria-hidden="true" focusable="false" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M2 4h12"/><path d="M2 12h3"/><path d="M2 8h9.5a2.5 2.5 0 010 5H8.5"/><path d="M10.5 10.75L8.5 13l2 2.25"/></svg>
688
+ </button>
689
+ <button type="button" id="toggle-unchanged-button" class="icon-button" title="Show changed areas only" aria-label="Show changed areas only">
690
+ <svg aria-hidden="true" focusable="false" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M2 4h12"/><path d="M2 12h12"/><path d="M5 7l3-3 3 3"/><path d="M5 9l3 3 3-3"/></svg>
691
+ </button>
692
+ <button type="button" id="file-comment-button" class="icon-button" title="Add file-level comment" aria-label="Add file comment">
693
+ <svg aria-hidden="true" focusable="false" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M2.75 2.5h10.5a.75.75 0 01.75.75v7.5a.75.75 0 01-.75.75H7l-3 3v-3H2.75a.75.75 0 01-.75-.75v-7.5a.75.75 0 01.75-.75z"/><path d="M8 6v3.5M6.25 7.75h3.5"/></svg>
694
+ </button>
695
+ <button type="button" id="toggle-reviewed-button" class="gh-viewed" title="Mark this file as viewed" aria-label="Mark as viewed">
696
+ <span class="gh-viewed-box"><svg aria-hidden="true" focusable="false" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M4 8.5l2.5 2.5L12 5.5"/></svg></span>
697
+ <span>Viewed</span>
698
+ </button>
699
+ </div>
700
+ </div>
701
+ <div id="file-comments-container" class="hidden border-b border-review-border bg-[#0d1117] px-4 py-0"></div>
702
+ <div id="editor-container" class="min-h-0 min-w-0 flex-1" data-text-editor-mode="diff" data-asset-failure="false">
703
+ <div id="diff-editor-host"></div>
704
+ <div id="single-editor-host"></div>
705
+ <div id="binary-preview" data-active="false"></div>
706
+ <div id="editor-cover" aria-hidden="true"></div>
707
+ <div id="asset-failure-panel" aria-live="polite" aria-hidden="true">
708
+ <div class="asset-failure-card">
709
+ <div class="asset-failure-eyebrow">Review UI unavailable</div>
710
+ <h2 id="asset-failure-title" class="asset-failure-title">The review window could not finish loading.</h2>
711
+ <p id="asset-failure-message" class="asset-failure-copy">TLH could not initialize its packaged review assets. Close this window and rerun <code>/annotate-git-diff</code>. If the problem persists, reinstall or update TLH.</p>
712
+ <pre id="asset-failure-detail" class="asset-failure-detail" hidden></pre>
713
+ </div>
714
+ </div>
715
+ </div>
716
+ </main>
717
+ </div>
718
+ </div>
719
+
720
+ <script id="annotate-git-diff-data" type="application/json">"__INLINE_DATA__"</script>
721
+ <script>
722
+ window.__reviewAssetConfig = __INLINE_ASSET_CONFIG__;
723
+ </script>
724
+ <script>
725
+ /**
726
+ * Keyboard shortcut bridge for WKWebView (glimpseui).
727
+ * glimpseui loads inline HTML as about:blank/null-origin content. That is not a
728
+ * secure context, so navigator.clipboard is unavailable and Cmd+C/V/X/A are not
729
+ * backed by a native Edit menu. We bridge clipboard reads/writes through the
730
+ * extension host and use DOM/Monaco APIs only to collect or insert text.
731
+ */
732
+ (() => {
733
+ const pendingClipboardReads = new Map();
734
+ let clipboardRequestSeq = 0;
735
+
736
+ function isTextControl(el) {
737
+ return el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement;
738
+ }
739
+
740
+ function isEditableTextControl(el) {
741
+ return isTextControl(el) && !el.disabled && !el.readOnly;
742
+ }
743
+
744
+ function isMonacoInput(el) {
745
+ return !!(el?.classList?.contains("inputarea") && el.closest?.(".monaco-editor"));
746
+ }
747
+
748
+ function getTextControlSelection(el) {
749
+ const start = el.selectionStart ?? 0;
750
+ const end = el.selectionEnd ?? start;
751
+ if (start === end) return "";
752
+ return el.value.slice(start, end);
753
+ }
754
+
755
+ function replaceTextControlSelection(el, text) {
756
+ const start = el.selectionStart ?? el.value.length;
757
+ const end = el.selectionEnd ?? el.value.length;
758
+ el.value = el.value.slice(0, start) + text + el.value.slice(end);
759
+ const cursor = start + text.length;
760
+ el.selectionStart = el.selectionEnd = cursor;
761
+ el.dispatchEvent(new Event("input", { bubbles: true }));
762
+ return true;
763
+ }
764
+
765
+ function getDomSelectionText() {
766
+ return window.getSelection?.().toString() ?? "";
767
+ }
768
+
769
+ function getSelectedText() {
770
+ const active = document.activeElement;
771
+ if (active && isTextControl(active) && !isMonacoInput(active)) {
772
+ return getTextControlSelection(active);
773
+ }
774
+ const editorText = window.__reviewClipboard?.getSelectedText?.() ?? "";
775
+ if (editorText) return editorText;
776
+ return getDomSelectionText();
777
+ }
778
+
779
+ function deleteSelectedText() {
780
+ const active = document.activeElement;
781
+ if (active && isEditableTextControl(active) && !isMonacoInput(active)) {
782
+ return replaceTextControlSelection(active, "");
783
+ }
784
+ if (active?.isContentEditable) {
785
+ return document.execCommand("delete");
786
+ }
787
+ return window.__reviewClipboard?.deleteSelection?.() === true;
788
+ }
789
+
790
+ function canReceivePaste() {
791
+ const active = document.activeElement;
792
+ if (!active) return false;
793
+ if (isMonacoInput(active)) return window.__reviewClipboard?.canPaste?.() === true;
794
+ return isEditableTextControl(active) || active.isContentEditable || window.__reviewClipboard?.canPaste?.() === true;
795
+ }
796
+
797
+ function insertAtActiveTarget(text) {
798
+ const active = document.activeElement;
799
+ if (active && isEditableTextControl(active) && !isMonacoInput(active)) {
800
+ return replaceTextControlSelection(active, text);
801
+ }
802
+ if (active?.isContentEditable) {
803
+ return document.execCommand("insertText", false, text);
804
+ }
805
+ return window.__reviewClipboard?.insertText?.(text) === true;
806
+ }
807
+
808
+ function writeClipboardText(text) {
809
+ if (!text) return;
810
+ if (window.glimpse?.send) {
811
+ window.glimpse.send({ type: "clipboard-write", text });
812
+ return;
813
+ }
814
+ if (navigator.clipboard?.writeText) {
815
+ void navigator.clipboard.writeText(text).catch(() => {});
816
+ }
817
+ }
818
+
819
+ function requestClipboardText() {
820
+ if (!window.glimpse?.send) {
821
+ if (navigator.clipboard?.readText) return navigator.clipboard.readText();
822
+ return Promise.resolve("");
823
+ }
824
+ const requestId = `clipboard:${Date.now()}:${++clipboardRequestSeq}`;
825
+ return new Promise((resolve) => {
826
+ const timeoutId = setTimeout(() => {
827
+ pendingClipboardReads.delete(requestId);
828
+ resolve("");
829
+ }, 3000);
830
+ pendingClipboardReads.set(requestId, { resolve, timeoutId });
831
+ window.glimpse.send({ type: "clipboard-read", requestId });
832
+ });
833
+ }
834
+
835
+ window.__reviewReceiveClipboardData = (message) => {
836
+ const pending = pendingClipboardReads.get(message?.requestId);
837
+ if (!pending) return;
838
+ clearTimeout(pending.timeoutId);
839
+ pendingClipboardReads.delete(message.requestId);
840
+ pending.resolve(typeof message.text === "string" ? message.text : "");
841
+ };
842
+
843
+ function handleCopyOrCut(e, cut) {
844
+ const text = getSelectedText();
845
+ if (!text) return;
846
+ e.preventDefault();
847
+ writeClipboardText(text);
848
+ if (cut) deleteSelectedText();
849
+ }
850
+
851
+ async function handlePaste(e) {
852
+ if (!canReceivePaste()) return;
853
+ e.preventDefault();
854
+ const text = await requestClipboardText();
855
+ if (text) insertAtActiveTarget(text);
856
+ }
857
+
858
+ function handleSelectAll(e) {
859
+ const active = document.activeElement;
860
+ if (active && isTextControl(active) && !isMonacoInput(active)) {
861
+ e.preventDefault();
862
+ active.select();
863
+ return;
864
+ }
865
+ if (window.__reviewClipboard?.selectAll?.() === true) {
866
+ e.preventDefault();
867
+ return;
868
+ }
869
+ if (active?.isContentEditable) {
870
+ e.preventDefault();
871
+ document.execCommand("selectAll");
872
+ }
873
+ }
874
+
875
+ document.addEventListener(
876
+ "keydown",
877
+ (e) => {
878
+ if (!(e.metaKey || e.ctrlKey)) return;
879
+ const key = e.key.toLowerCase();
880
+ if (key === "w") {
881
+ e.preventDefault();
882
+ if (window.glimpse && typeof window.glimpse.close === "function") window.glimpse.close();
883
+ else if (typeof window.close === "function") window.close();
884
+ return;
885
+ }
886
+ if (key === "v") {
887
+ void handlePaste(e);
888
+ return;
889
+ }
890
+ if (key === "c") {
891
+ handleCopyOrCut(e, false);
892
+ return;
893
+ }
894
+ if (key === "x") {
895
+ handleCopyOrCut(e, true);
896
+ return;
897
+ }
898
+ if (key === "a") {
899
+ handleSelectAll(e);
900
+ }
901
+ },
902
+ true,
903
+ );
904
+ })();
905
+ </script>
906
+ <script>
907
+ __INLINE_MONACO_LOADER_JS__
908
+ </script>
909
+ <script>
910
+ __INLINE_JS__
911
+ </script>
912
+ </body>
913
+ </html>