@plannotator/pi-extension 0.19.12 → 0.19.14

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,717 @@
1
+ # PR Component Patterns
2
+
3
+ Component patterns specific to PR explainer documents. For the base design system (colors, typography, layout), see `../../plannotator-visual-plan/references/design-system.md`.
4
+
5
+ ## Table of Contents
6
+ 1. [PR Header](#pr-header)
7
+ 2. [TL;DR Box](#tldr-box)
8
+ 3. [Diff Rendering](#diff-rendering)
9
+ 4. [Review Comments](#review-comments)
10
+ 5. [Risk Map](#risk-map)
11
+ 6. [File Cards](#file-cards)
12
+ 7. [Before / After](#before--after)
13
+ 8. [Where to Focus](#where-to-focus)
14
+ 9. [Test Plan](#test-plan)
15
+ 10. [File Badges](#file-badges)
16
+
17
+ ## PR header
18
+
19
+ ```html
20
+ <header>
21
+ <span class="eyebrow">Pull request · repo-name</span>
22
+ <h1>Add real-time notification system</h1>
23
+ <div class="pr-meta">
24
+ <span>6 files</span>
25
+ <span class="additions">+142</span>
26
+ <span class="deletions">-38</span>
27
+ <span>feature/notifications → main</span>
28
+ </div>
29
+ </header>
30
+ ```
31
+
32
+ ```css
33
+ .pr-meta {
34
+ display: flex;
35
+ flex-wrap: wrap;
36
+ gap: 16px;
37
+ font-family: var(--font-mono);
38
+ font-size: 0.78rem;
39
+ color: var(--muted-foreground);
40
+ margin-top: 8px;
41
+ }
42
+
43
+ .pr-meta .additions { color: var(--success); }
44
+ .pr-meta .deletions { color: var(--destructive); }
45
+ ```
46
+
47
+ ## TL;DR box
48
+
49
+ ```html
50
+ <div class="tldr">
51
+ <h3>TL;DR</h3>
52
+ <p>Adds WebSocket-based notifications with per-user channels. Messages fan out
53
+ from a new NotificationService through Redis pub/sub. Existing REST endpoints
54
+ are unchanged.</p>
55
+ </div>
56
+ ```
57
+
58
+ ```css
59
+ .tldr {
60
+ background: var(--card);
61
+ border: 1.5px solid var(--border);
62
+ border-left: 4px solid var(--primary);
63
+ border-radius: var(--radius);
64
+ padding: 20px 24px;
65
+ max-width: 760px;
66
+ margin: 24px 0;
67
+ }
68
+
69
+ .tldr h3 {
70
+ font-family: var(--font-mono);
71
+ font-size: 0.72rem;
72
+ font-weight: 600;
73
+ text-transform: uppercase;
74
+ letter-spacing: 0.06em;
75
+ color: var(--primary);
76
+ margin-bottom: 8px;
77
+ }
78
+
79
+ .tldr p {
80
+ font-size: 0.95rem;
81
+ line-height: 1.6;
82
+ color: var(--muted-foreground);
83
+ }
84
+ ```
85
+
86
+ ## Diff rendering
87
+
88
+ Use Pierre diffs via CDN for syntax-highlighted, theme-aware diff rendering. Renders into shadow DOM (no style conflicts) with Shiki syntax highlighting.
89
+
90
+ ```html
91
+ <script type="module">
92
+ import { getSingularPatch, registerDiffsComponent } from 'https://cdn.jsdelivr.net/npm/@pierre/diffs@1.1.21/+esm';
93
+ registerDiffsComponent();
94
+
95
+ const patch = `--- a/src/handler.ts
96
+ +++ b/src/handler.ts
97
+ @@ -1,4 +1,6 @@
98
+ import { Router } from 'express';
99
+ +import { NotificationService } from './notifications';
100
+ -import { legacyPoll } from './polling';`;
101
+
102
+ const container = document.querySelector('diffs-container');
103
+ container.fileDiff = getSingularPatch(patch);
104
+ container.options = {
105
+ themeType: window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light',
106
+ diffStyle: 'unified',
107
+ diffIndicators: 'bars',
108
+ lineDiffType: 'word-alt',
109
+ unsafeCSS: `
110
+ :host {
111
+ --diffs-bg: var(--background);
112
+ --diffs-fg: var(--foreground);
113
+ border-radius: var(--radius);
114
+ border: 1.5px solid var(--border);
115
+ overflow: hidden;
116
+ }
117
+ `,
118
+ };
119
+ </script>
120
+ <diffs-container></diffs-container>
121
+ ```
122
+
123
+ For multiple diffs, create one `<diffs-container>` per file. Pierre handles syntax highlighting, line numbers, add/del coloring, and word-level diffs automatically.
124
+
125
+ ## Review comments
126
+
127
+ Speech bubbles with severity-coded left borders, attached below a diff block.
128
+
129
+ ```html
130
+ <div class="comments">
131
+ <div class="bubble blocking">
132
+ <span class="anchor">line 11</span>
133
+ <span class="severity">BLOCKING</span>
134
+ <p>This mutation isn't wrapped in a transaction. If the second write
135
+ fails, the first persists — leaving the user in a broken state.</p>
136
+ </div>
137
+ <div class="bubble nit">
138
+ <span class="anchor">line 24</span>
139
+ <span class="severity">NIT</span>
140
+ <p>Prefer <code>const</code> here since <code>config</code> is never reassigned.</p>
141
+ </div>
142
+ </div>
143
+ ```
144
+
145
+ ```css
146
+ .comments {
147
+ padding: 18px 20px;
148
+ display: flex;
149
+ flex-direction: column;
150
+ gap: 14px;
151
+ background: var(--muted);
152
+ border-top: 1px solid var(--border);
153
+ }
154
+
155
+ .bubble {
156
+ position: relative;
157
+ background: var(--card);
158
+ border: 1.5px solid var(--border);
159
+ border-left-width: 4px;
160
+ border-radius: 8px;
161
+ padding: 12px 14px 12px 16px;
162
+ max-width: 680px;
163
+ }
164
+
165
+ .bubble.blocking { border-left-color: var(--primary); }
166
+ .bubble.nit { border-left-color: var(--border); }
167
+ .bubble.suggestion { border-left-color: var(--success); }
168
+
169
+ .bubble::before {
170
+ content: "";
171
+ position: absolute;
172
+ left: -9px;
173
+ top: 16px;
174
+ width: 12px;
175
+ height: 12px;
176
+ background: var(--card);
177
+ border-left: 1.5px solid var(--border);
178
+ border-bottom: 1.5px solid var(--border);
179
+ transform: rotate(45deg);
180
+ }
181
+
182
+ .bubble.blocking::before {
183
+ border-left-color: var(--primary);
184
+ border-bottom-color: var(--primary);
185
+ }
186
+
187
+ .anchor {
188
+ font-family: var(--font-mono);
189
+ font-size: 0.68rem;
190
+ color: var(--muted-foreground);
191
+ }
192
+
193
+ .severity {
194
+ font-family: var(--font-mono);
195
+ font-size: 0.65rem;
196
+ font-weight: 700;
197
+ text-transform: uppercase;
198
+ letter-spacing: 0.06em;
199
+ margin-left: 8px;
200
+ }
201
+
202
+ .bubble.blocking .severity { color: var(--primary); }
203
+ .bubble.nit .severity { color: var(--muted-foreground); }
204
+ .bubble.suggestion .severity { color: var(--success); }
205
+
206
+ .bubble p {
207
+ margin-top: 6px;
208
+ font-size: 0.88rem;
209
+ line-height: 1.55;
210
+ color: var(--foreground);
211
+ }
212
+
213
+ .bubble code {
214
+ font-family: var(--font-mono);
215
+ font-size: 0.82rem;
216
+ background: var(--muted);
217
+ padding: 1px 5px;
218
+ border-radius: 3px;
219
+ }
220
+ ```
221
+
222
+ ## Risk map
223
+
224
+ Chips that give a quick overview of file risk levels.
225
+
226
+ ```html
227
+ <div class="risk-map">
228
+ <a href="#file-auth" class="chip attention">
229
+ <span class="dot"></span>
230
+ src/auth/middleware.ts
231
+ </a>
232
+ <a href="#file-db" class="chip medium">
233
+ <span class="dot"></span>
234
+ src/db/migrations/004.sql
235
+ </a>
236
+ <a href="#file-types" class="chip safe">
237
+ <span class="dot"></span>
238
+ src/types/index.ts
239
+ </a>
240
+ </div>
241
+ ```
242
+
243
+ ```css
244
+ .risk-map {
245
+ display: flex;
246
+ flex-wrap: wrap;
247
+ gap: 8px;
248
+ margin: 24px 0;
249
+ }
250
+
251
+ .chip {
252
+ display: inline-flex;
253
+ align-items: center;
254
+ gap: 8px;
255
+ font-family: var(--font-mono);
256
+ font-size: 0.75rem;
257
+ padding: 6px 12px;
258
+ border: 1.5px solid var(--border);
259
+ border-radius: 20px;
260
+ text-decoration: none;
261
+ color: var(--foreground);
262
+ transition: box-shadow 0.15s;
263
+ }
264
+
265
+ .chip:hover {
266
+ box-shadow: 0 0 0 2px color-mix(in oklab, var(--primary) 25%, transparent);
267
+ }
268
+
269
+ .chip .dot {
270
+ width: 9px;
271
+ height: 9px;
272
+ border-radius: 50%;
273
+ }
274
+
275
+ .chip.attention {
276
+ background: color-mix(in oklab, var(--destructive) 8%, transparent);
277
+ border-color: color-mix(in oklab, var(--destructive) 40%, transparent);
278
+ }
279
+ .chip.attention .dot { background: var(--destructive); }
280
+
281
+ .chip.medium {
282
+ background: color-mix(in oklab, var(--warning) 10%, transparent);
283
+ border-color: color-mix(in oklab, var(--warning) 30%, transparent);
284
+ }
285
+ .chip.medium .dot { background: var(--warning); }
286
+
287
+ .chip.safe {
288
+ background: color-mix(in oklab, var(--success) 8%, transparent);
289
+ border-color: color-mix(in oklab, var(--success) 35%, transparent);
290
+ }
291
+ .chip.safe .dot { background: var(--success); }
292
+ ```
293
+
294
+ ## File cards
295
+
296
+ Expandable cards grouping a file's diff and review commentary.
297
+
298
+ ```html
299
+ <div class="file-card" id="file-auth">
300
+ <div class="file-head">
301
+ <div class="file-info">
302
+ <span class="file-path">src/auth/middleware.ts</span>
303
+ <span class="file-badge mod">MOD</span>
304
+ <span class="file-stats"><span class="additions">+28</span> <span class="deletions">-12</span></span>
305
+ </div>
306
+ <span class="risk-tag attention">ATTENTION</span>
307
+ </div>
308
+ <div class="file-why">
309
+ <p>Replaced session-cookie auth with JWT verification. The trust boundary
310
+ moves from the session store to the token signature check.</p>
311
+ </div>
312
+ <div class="diff"><!-- diff rows --></div>
313
+ <div class="comments"><!-- review bubbles --></div>
314
+ </div>
315
+
316
+ <!-- Safe files: collapsed -->
317
+ <details class="file-collapsed">
318
+ <summary>
319
+ <span class="file-path">src/types/index.ts</span>
320
+ <span class="file-badge mod">MOD</span>
321
+ <span class="file-stats"><span class="additions">+4</span> <span class="deletions">-0</span></span>
322
+ <span class="risk-tag safe">SAFE</span>
323
+ </summary>
324
+ <div class="file-why">
325
+ <p>Added NotificationPayload type export.</p>
326
+ </div>
327
+ </details>
328
+ ```
329
+
330
+ ```css
331
+ .file-card {
332
+ border: 1.5px solid var(--border);
333
+ border-radius: var(--radius);
334
+ background: var(--card);
335
+ overflow: hidden;
336
+ scroll-margin-top: 20px;
337
+ margin: 16px 0;
338
+ }
339
+
340
+ .file-head {
341
+ padding: 16px 20px;
342
+ border-bottom: 1.5px solid var(--border);
343
+ display: flex;
344
+ align-items: center;
345
+ justify-content: space-between;
346
+ }
347
+
348
+ .file-info {
349
+ display: flex;
350
+ align-items: center;
351
+ gap: 12px;
352
+ }
353
+
354
+ .file-path {
355
+ font-family: var(--font-mono);
356
+ font-size: 0.82rem;
357
+ font-weight: 600;
358
+ }
359
+
360
+ .file-stats {
361
+ font-family: var(--font-mono);
362
+ font-size: 0.72rem;
363
+ }
364
+
365
+ .file-stats .additions { color: var(--success); }
366
+ .file-stats .deletions { color: var(--destructive); }
367
+
368
+ .file-why {
369
+ padding: 12px 20px;
370
+ border-bottom: 1px solid var(--border);
371
+ }
372
+
373
+ .file-why p {
374
+ font-size: 0.9rem;
375
+ color: var(--muted-foreground);
376
+ line-height: 1.55;
377
+ }
378
+
379
+ /* Collapsed (safe) files */
380
+ .file-collapsed {
381
+ border: 1.5px solid var(--border);
382
+ border-radius: var(--radius);
383
+ background: var(--card);
384
+ margin: 8px 0;
385
+ }
386
+
387
+ .file-collapsed summary {
388
+ list-style: none;
389
+ cursor: pointer;
390
+ padding: 14px 20px;
391
+ display: flex;
392
+ align-items: center;
393
+ gap: 12px;
394
+ }
395
+
396
+ .file-collapsed summary::after {
397
+ content: '+';
398
+ font-family: var(--font-mono);
399
+ font-size: 0.85rem;
400
+ color: var(--muted-foreground);
401
+ margin-left: auto;
402
+ }
403
+
404
+ .file-collapsed[open] summary::after {
405
+ content: '\2212';
406
+ }
407
+ ```
408
+
409
+ ## Before / after
410
+
411
+ Two-column comparison grid.
412
+
413
+ ```html
414
+ <div class="before-after">
415
+ <div class="ba-panel before">
416
+ <h4>Before</h4>
417
+ <p>Auth checked via session cookie on every request.
418
+ Session store hit adds ~15ms latency.</p>
419
+ </div>
420
+ <div class="ba-panel after">
421
+ <h4>After</h4>
422
+ <p>JWT signature verified in-process. No external store hit.
423
+ Latency drops to ~1ms per request.</p>
424
+ </div>
425
+ </div>
426
+ ```
427
+
428
+ ```css
429
+ .before-after {
430
+ display: grid;
431
+ grid-template-columns: 1fr 1fr;
432
+ gap: 16px;
433
+ margin: 16px 0;
434
+ }
435
+
436
+ @media (max-width: 640px) {
437
+ .before-after { grid-template-columns: 1fr; }
438
+ }
439
+
440
+ .ba-panel {
441
+ background: var(--card);
442
+ border: 1.5px solid var(--border);
443
+ border-radius: var(--radius);
444
+ padding: 18px 20px;
445
+ }
446
+
447
+ .ba-panel.after {
448
+ border-color: var(--success);
449
+ }
450
+
451
+ .ba-panel h4 {
452
+ font-family: var(--font-mono);
453
+ font-size: 0.72rem;
454
+ font-weight: 600;
455
+ text-transform: uppercase;
456
+ letter-spacing: 0.06em;
457
+ margin-bottom: 8px;
458
+ }
459
+
460
+ .ba-panel.before h4 { color: var(--muted-foreground); }
461
+ .ba-panel.after h4 { color: var(--success); }
462
+
463
+ .ba-panel p {
464
+ font-size: 0.9rem;
465
+ line-height: 1.55;
466
+ color: var(--foreground);
467
+ }
468
+ ```
469
+
470
+ ## Where to focus
471
+
472
+ Numbered callout cards directing reviewers.
473
+
474
+ ```html
475
+ <div class="focus-list">
476
+ <div class="focus-item">
477
+ <span class="focus-number">1</span>
478
+ <div>
479
+ <strong>src/auth/middleware.ts:verifyToken()</strong>
480
+ <p>New trust boundary. Verify the JWT validation covers all edge cases:
481
+ expired tokens, malformed signatures, missing claims.</p>
482
+ </div>
483
+ </div>
484
+ </div>
485
+ ```
486
+
487
+ ```css
488
+ .focus-list {
489
+ display: flex;
490
+ flex-direction: column;
491
+ gap: 12px;
492
+ margin: 16px 0;
493
+ }
494
+
495
+ .focus-item {
496
+ background: var(--card);
497
+ border: 1.5px solid var(--border);
498
+ border-radius: var(--radius);
499
+ padding: 16px 20px;
500
+ display: flex;
501
+ gap: 16px;
502
+ align-items: flex-start;
503
+ }
504
+
505
+ .focus-number {
506
+ font-family: var(--font-mono);
507
+ font-size: 0.78rem;
508
+ font-weight: 600;
509
+ color: var(--primary-foreground);
510
+ background: var(--primary);
511
+ width: 26px;
512
+ height: 26px;
513
+ border-radius: 50%;
514
+ display: flex;
515
+ align-items: center;
516
+ justify-content: center;
517
+ flex-shrink: 0;
518
+ }
519
+
520
+ .focus-item strong {
521
+ font-family: var(--font-mono);
522
+ font-size: 0.82rem;
523
+ display: block;
524
+ margin-bottom: 4px;
525
+ }
526
+
527
+ .focus-item p {
528
+ font-size: 0.88rem;
529
+ color: var(--muted-foreground);
530
+ line-height: 1.5;
531
+ }
532
+ ```
533
+
534
+ ## Test plan
535
+
536
+ Checkbox-style verification checklist.
537
+
538
+ ```html
539
+ <div class="test-list">
540
+ <div class="test-item done">
541
+ <span class="check"></span>
542
+ <span>Expired JWT returns 401 with proper error body</span>
543
+ </div>
544
+ <div class="test-item">
545
+ <span class="check"></span>
546
+ <span>Concurrent WebSocket connections scale to 1000 without memory leak</span>
547
+ </div>
548
+ </div>
549
+ ```
550
+
551
+ ```css
552
+ .test-list {
553
+ display: flex;
554
+ flex-direction: column;
555
+ gap: 8px;
556
+ margin: 16px 0;
557
+ }
558
+
559
+ .test-item {
560
+ display: flex;
561
+ align-items: flex-start;
562
+ gap: 12px;
563
+ background: var(--card);
564
+ border: 1.5px solid var(--border);
565
+ border-radius: var(--radius);
566
+ padding: 13px 18px;
567
+ font-size: 0.88rem;
568
+ }
569
+
570
+ .check {
571
+ width: 18px;
572
+ height: 18px;
573
+ border-radius: 5px;
574
+ border: 1.5px solid var(--border);
575
+ flex-shrink: 0;
576
+ position: relative;
577
+ margin-top: 2px;
578
+ }
579
+
580
+ .test-item.done .check {
581
+ background: var(--success);
582
+ border-color: var(--success);
583
+ }
584
+
585
+ .test-item.done .check::after {
586
+ content: '';
587
+ position: absolute;
588
+ left: 5px;
589
+ top: 2px;
590
+ width: 5px;
591
+ height: 9px;
592
+ border-right: 2px solid var(--card);
593
+ border-bottom: 2px solid var(--card);
594
+ transform: rotate(40deg);
595
+ }
596
+ ```
597
+
598
+ ## File badges
599
+
600
+ ```css
601
+ .file-badge {
602
+ font-family: var(--font-mono);
603
+ font-size: 0.62rem;
604
+ font-weight: 700;
605
+ text-transform: uppercase;
606
+ letter-spacing: 0.04em;
607
+ padding: 2px 6px;
608
+ border-radius: calc(var(--radius) - 4px);
609
+ }
610
+
611
+ .file-badge.new {
612
+ background: color-mix(in oklab, var(--success) 15%, transparent);
613
+ color: var(--success);
614
+ }
615
+
616
+ .file-badge.mod {
617
+ background: color-mix(in oklab, var(--warning) 15%, transparent);
618
+ color: var(--warning);
619
+ }
620
+
621
+ .file-badge.del {
622
+ background: color-mix(in oklab, var(--destructive) 15%, transparent);
623
+ color: var(--destructive);
624
+ }
625
+
626
+ .risk-tag {
627
+ font-family: var(--font-mono);
628
+ font-size: 0.65rem;
629
+ font-weight: 600;
630
+ text-transform: uppercase;
631
+ letter-spacing: 0.06em;
632
+ padding: 3px 8px;
633
+ border-radius: calc(var(--radius) - 4px);
634
+ }
635
+
636
+ .risk-tag.attention {
637
+ background: color-mix(in oklab, var(--destructive) 12%, transparent);
638
+ color: var(--destructive);
639
+ }
640
+
641
+ .risk-tag.medium {
642
+ background: color-mix(in oklab, var(--warning) 12%, transparent);
643
+ color: var(--warning);
644
+ }
645
+
646
+ .risk-tag.safe {
647
+ background: color-mix(in oklab, var(--success) 12%, transparent);
648
+ color: var(--success);
649
+ }
650
+ ```
651
+
652
+ ## Rollout plan
653
+
654
+ Phased deployment strip showing ramp percentages.
655
+
656
+ ```html
657
+ <div class="rollout">
658
+ <div class="rollout-step">
659
+ <div class="rollout-when">Day 0</div>
660
+ <div class="rollout-pct">internal</div>
661
+ <div class="rollout-desc">Team only. Watch error rates.</div>
662
+ </div>
663
+ <div class="rollout-step">
664
+ <div class="rollout-when">Day 2</div>
665
+ <div class="rollout-pct">10%</div>
666
+ <div class="rollout-desc">Random sample. Alert on anomalies.</div>
667
+ </div>
668
+ <div class="rollout-step">
669
+ <div class="rollout-when">Day 4</div>
670
+ <div class="rollout-pct">100%</div>
671
+ <div class="rollout-desc">Full ramp.</div>
672
+ </div>
673
+ </div>
674
+ ```
675
+
676
+ ```css
677
+ .rollout { display: flex; gap: 0; }
678
+
679
+ .rollout-step {
680
+ flex: 1;
681
+ background: var(--card);
682
+ border: 1.5px solid var(--border);
683
+ padding: 16px 18px;
684
+ }
685
+
686
+ .rollout-step:first-child { border-radius: var(--radius) 0 0 var(--radius); }
687
+ .rollout-step:last-child { border-radius: 0 var(--radius) var(--radius) 0; }
688
+ .rollout-step + .rollout-step { border-left: none; }
689
+
690
+ .rollout-when {
691
+ font-family: var(--font-mono);
692
+ font-size: 0.68rem;
693
+ text-transform: uppercase;
694
+ letter-spacing: 0.05em;
695
+ color: var(--muted-foreground);
696
+ margin-bottom: 8px;
697
+ }
698
+
699
+ .rollout-pct {
700
+ font-family: var(--font-mono);
701
+ font-size: 1.35rem;
702
+ font-weight: 600;
703
+ color: var(--primary);
704
+ margin-bottom: 6px;
705
+ }
706
+
707
+ .rollout-desc {
708
+ font-size: 0.82rem;
709
+ color: var(--muted-foreground);
710
+ }
711
+
712
+ @media (max-width: 720px) {
713
+ .rollout { flex-direction: column; }
714
+ .rollout-step { border-radius: var(--radius); }
715
+ .rollout-step + .rollout-step { border-left: 1.5px solid var(--border); margin-top: 10px; }
716
+ }
717
+ ```