Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

pr-redesign.tsx

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

pr-redesign.tsxBlame1511 lines · 2 contributors
11c3ab6ccanty labs1import type { FC } from "hono/jsx";
2
3export interface PrRedesignProps {
4 pr?: {
5 number?: number;
6 title?: string;
7 state?: string;
8 baseBranch?: string;
9 headBranch?: string;
10 body?: string;
11 };
12 checks?: Array<{
13 name: string;
14 detail: string;
15 state: string;
16 dot?: string;
17 stateColor?: string;
18 }>;
19 aiReview?: any;
20 owner?: string;
21 repo?: string;
22 user?: any;
23}
24
25const defaultPr = {
26 number: 218,
27 title: "Unify auto-repair loop with PR sandbox lifecycle",
28 state: "open",
29 baseBranch: "main",
30 headBranch: "feat/repair-sandbox-loop",
31 body: "",
32};
33
34const defaultChecks = [
35 { name: "gate: security", detail: "15-pattern secret scan + AI security review", dot: "#1e7f5c", state: "Passed", stateColor: "#1e7f5c" },
36 { name: "gate: tests", detail: "bun test · 412 passed", dot: "#1e7f5c", state: "Passed", stateColor: "#1e7f5c" },
37 { name: "gate: types", detail: "tsc --noEmit", dot: "#1e7f5c", state: "Passed", stateColor: "#1e7f5c" },
38 { name: "review: claude", detail: "trio review · 2 pass, 1 flag", dot: "#b45309", state: "Needs human", stateColor: "#b45309" },
39 { name: "sandbox: boot", detail: "pr-218.preview.gluecron.com", dot: "#1e7f5c", state: "Live", stateColor: "#1e7f5c" },
40];
41
42const trioData = [
43 {
44 icon: "S",
45 iconLabel: "shield",
46 name: "Security",
47 verdict: "Pass",
48 verdictBg: "rgba(30,127,92,0.10)",
49 verdictColor: "#1e7f5c",
50 verdictBorder: "rgba(30,127,92,0.30)",
51 body: "Repair patches are applied inside the sandbox with no host filesystem access. Telemetry writes are parameterized. No new secrets surface.",
52 },
53 {
54 icon: "P",
55 iconLabel: "performance",
56 name: "Performance",
57 verdict: "Flag",
58 verdictBg: "rgba(180,83,9,0.10)",
59 verdictColor: "#b45309",
60 verdictBorder: "rgba(180,83,9,0.28)",
61 body: "Retry backoff in repairLoop() has no upper bound — a persistently red gate could hold a sandbox container for hours. Suggest capping at 5 attempts.",
62 },
63 {
64 icon: "St",
65 iconLabel: "style",
66 name: "Style",
67 verdict: "Pass",
68 verdictBg: "rgba(30,127,92,0.10)",
69 verdictColor: "#1e7f5c",
70 verdictBorder: "rgba(30,127,92,0.30)",
71 body: "Naming and module layout match the existing lib/ conventions. JSDoc on new public functions is complete.",
72 },
73];
74
75const changedFiles = [
76 { mark: "M", color: "#b45309", path: "src/lib/auto-repair.ts", add: 184, del: 62 },
77 { mark: "M", color: "#b45309", path: "src/lib/pr-sandbox.ts", add: 97, del: 18 },
78 { mark: "M", color: "#b45309", path: "src/lib/ai-ci-healer.ts", add: 41, del: 12 },
79 { mark: "A", color: "#1e7f5c", path: "drizzle/0107_auto_repair_mode.sql", add: 14, del: 0 },
80 { mark: "M", color: "#b45309", path: "src/hooks/post-receive.ts", add: 22, del: 6 },
81];
82
83export const PrRedesign: FC<PrRedesignProps> = (props) => {
84 const pr = { ...defaultPr, ...(props.pr || {}) };
85 const checks = props.checks && props.checks.length > 0 ? props.checks : defaultChecks;
86 const owner = props.owner || "ccantynz";
87 const repo = props.repo || "gluecron";
88 const user = props.user || { name: "ccantynz", initials: "C" };
89
90 const isOpen = pr.state === "open" || pr.state === "Open";
91
92 return (
93 <>
94 <style dangerouslySetInnerHTML={{ __html: css }} />
95 <div class="pr-root">
96 {/* Top nav */}
97 <header class="pr-header">
98 <nav class="pr-nav">
99 <a href="/" class="pr-logo">
100 <span class="pr-logo-mark" />
101 gluecron
102 </a>
103 <div class="pr-search-wrap">
104 <span class="pr-search-icon" aria-hidden="true" />
105 <input type="search" placeholder="Search (press /)" aria-label="Search" class="pr-search-input" />
106 </div>
107 <div class="pr-nav-right">
108 <a href="/explore" class="pr-nav-link">Explore</a>
109 <a href="/notifications" title="Inbox" class="pr-nav-icon-btn" aria-label="Inbox">
110 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
111 <path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" />
112 <path d="M13.73 21a2 2 0 0 1-3.46 0" />
113 </svg>
114 <span class="pr-notif-badge">3</span>
115 </a>
116 <a href="/new" class="pr-new-btn">+ New</a>
117 <button type="button" class="pr-user-btn">
118 <span class="pr-user-avatar">{user.initials || "C"}</span>
119 <span>{user.name || "ccantynz"}</span>
120 <span class="pr-user-caret" aria-hidden="true">&#9662;</span>
121 </button>
122 </div>
123 </nav>
124 </header>
125
126 {/* Main */}
127 <main class="pr-main">
128 {/* Breadcrumb */}
129 <div class="pr-breadcrumb">
130 <strong class="pr-breadcrumb-accent">Pull request</strong>
131 {" · "}
132 {owner}/{repo}
133 </div>
134
135 {/* Title row */}
136 <div class="pr-title-row">
137 <h1 class="pr-title">
138 {pr.title}
139 <span class="pr-title-number"> #{pr.number}</span>
140 </h1>
141 <div class="pr-title-actions">
142 <span class={`pr-state-badge${isOpen ? " pr-state-open" : " pr-state-closed"}`}>
143 {isOpen ? "○ Open" : "● Closed"}
144 </span>
145 <a href="#" class="pr-edit-btn">Edit</a>
146 </div>
147 </div>
148
149 {/* Meta row */}
150 <div class="pr-meta-row">
151 <span class="pr-branches">
152 <code class="pr-branch-chip">{pr.headBranch}</code>
153 <span class="pr-branch-arrow">&#8594;</span>
154 <code class="pr-branch-chip">{pr.baseBranch}</code>
155 </span>
156 <span class="pr-meta-dot">·</span>
157 <span>
158 <strong class="pr-meta-strong">claude[agent]</strong> wants to merge 6 commits
159 </span>
160 <span class="pr-meta-dot">·</span>
161 <span>opened 38 min ago</span>
162 <span class="pr-presence" aria-label="Live session participants">
163 <span class="pr-presence-pill pr-presence-human" title="ccantynz">
164 <span class="pr-presence-av pr-presence-av-human">CC</span>
165 ccantynz
166 </span>
167 <span class="pr-presence-pill pr-presence-agent" title="claude[agent]">
168 <span class="pr-presence-av pr-presence-av-agent">A</span>
169 claude[agent]
170 </span>
171 </span>
172 </div>
173
174 {/* Tabs */}
175 <div class="pr-tabs" role="tablist">
176 <a href="#overview" class="pr-tab pr-tab-active" role="tab">
177 Overview
178 <span class="pr-tab-badge">8</span>
179 </a>
180 <a href="#files" class="pr-tab" role="tab">
181 Files changed
182 <span class="pr-tab-badge">11</span>
183 </a>
184 <a href="#activity" class="pr-tab" role="tab">
185 Activity
186 <span class="pr-tab-badge">6</span>
187 </a>
188 <a href="#checks" class="pr-tab" role="tab">
189 Checks
190 <span class="pr-tab-badge">5</span>
191 </a>
192 </div>
193
194 {/* Two-column grid */}
195 <div class="pr-grid">
196 {/* Main column */}
197 <div class="pr-main-col">
198
199 {/* PR Description card */}
200 <div class="pr-card">
201 <div class="pr-card-header">
202 <span class="pr-av pr-av-agent">CL</span>
203 <strong class="pr-card-author">claude[agent]</strong>
204 <span class="pr-card-meta"> commented 38 min ago</span>
205 <span class="pr-agent-badge">Agent</span>
206 </div>
207 <div class="pr-card-body pr-description">
208 <p>
209 Closes{" "}
210 <a href="#" class="pr-link">#204</a>
211 {". "}
212 When a gate fails on a PR branch, the auto-repair engine now attaches to the existing PR sandbox instead of spinning a detached loop:
213 </p>
214 <ul class="pr-desc-list">
215 <li>
216 <code class="pr-code">src/lib/auto-repair.ts</code> — repair attempts run inside the sandbox container
217 </li>
218 <li>
219 <code class="pr-code">src/lib/pr-sandbox.ts</code> — sandbox lifecycle exposes a repair session handle
220 </li>
221 <li>
222 <code class="pr-code">drizzle/0107_auto_repair_mode.sql</code> — per-repo repair mode column
223 </li>
224 </ul>
225 <p>
226 Each repair attempt is recorded in{" "}
227 <code class="pr-code">flywheel_telemetry</code>
228 {" "}with the failing gate, patch diff, and outcome.
229 </p>
230 </div>
231 </div>
232
233 {/* Merge Impact panel */}
234 <div class="pr-card pr-impact-card" id="pr-impact-card">
8ed88f2ccantynz-alt235 <div class="pr-impact-header" id="pr-impact-toggle" role="button" tabindex={0} aria-expanded="false" aria-controls="pr-impact-body">
11c3ab6ccanty labs236 <span class="pr-impact-risk-badge">62</span>
237 <strong class="pr-impact-title">Merge Impact</strong>
238 <span class="pr-impact-summary">
239 Medium risk — repair loop touches gate execution path shared by 3 subsystems
240 </span>
241 <button type="button" class="pr-impact-caret" id="pr-impact-caret" aria-label="Toggle impact panel">&#9660;</button>
242 </div>
243 <div class="pr-impact-body" id="pr-impact-body" style="display:none">
244 <div class="pr-impact-section">
245 <h4 class="pr-impact-section-title">Affected test files (3)</h4>
246 <ul class="pr-impact-list">
247 <li>src/__tests__/auto-repair.test.ts</li>
248 <li>src/__tests__/pr-sandbox.test.ts</li>
249 <li>src/__tests__/gates-runner.test.ts</li>
250 </ul>
251 </div>
252 <div class="pr-impact-section">
253 <h4 class="pr-impact-section-title">Affected source files (4)</h4>
254 <ul class="pr-impact-list">
255 <li>src/lib/ai-ci-healer.ts</li>
256 <li>src/lib/autopilot.ts</li>
257 <li>src/hooks/post-receive.ts</li>
258 <li>src/routes/gates.tsx</li>
259 </ul>
260 </div>
261 </div>
262 </div>
263
264 {/* AI Trio Review */}
265 <div class="pr-trio-card">
266 <div class="pr-trio-header">
267 <span class="pr-trio-dot" aria-hidden="true" />
268 <strong class="pr-trio-title">AI Trio Review</strong>
269 <span class="pr-trio-sub">Three independent reviewers ran in parallel.</span>
270 </div>
271 <div class="pr-trio-grid">
272 {trioData.map((t) => (
273 <div class="pr-trio-item" key={t.name}>
274 <div class="pr-trio-item-header">
275 <span class="pr-trio-item-icon" aria-hidden="true">{t.icon}</span>
276 <strong class="pr-trio-item-name">{t.name}</strong>
277 <span
278 class="pr-trio-verdict"
279 style={`background:${t.verdictBg};color:${t.verdictColor};border:1px solid ${t.verdictBorder}`}
280 >
281 {t.verdict}
282 </span>
283 </div>
284 <p class="pr-trio-item-body">{t.body}</p>
285 </div>
286 ))}
287 </div>
288 <div class="pr-trio-disagree" role="note">
289 <span class="pr-trio-warn-icon" aria-hidden="true">!</span>
290 <div>
291 <strong>Reviewers disagree — review carefully.</strong>{" "}
292 Performance flags the retry backoff as unbounded; Security and Style pass. Human review requested before auto-merge.
293 </div>
294 </div>
295 </div>
296
297 {/* Checks */}
298 <div class="pr-card pr-checks-card">
299 <div class="pr-checks-title">Checks — run #1842 · 42s</div>
300 <div class="pr-checks-list">
301 {checks.map((c) => (
302 <div class="pr-check-row" key={c.name}>
303 <span
304 class="pr-check-dot"
305 style={`background:${c.dot || "#c4c6cf"}`}
306 aria-hidden="true"
307 />
308 <span class="pr-check-name">{c.name}</span>
309 <span class="pr-check-detail">{c.detail}</span>
310 <span
311 class="pr-check-state"
312 style={`color:${c.stateColor || "#6b7080"}`}
313 >
314 {c.state}
315 </span>
316 </div>
317 ))}
318 </div>
319 </div>
320
321 {/* Merge box */}
322 <div class="pr-merge-box">
323 <span class="pr-merge-icon" aria-hidden="true">&#10003;</span>
324 <div class="pr-merge-content">
325 <div class="pr-merge-title">Ready to merge once you approve</div>
326 <div class="pr-merge-desc">
327 Gates green · 2 of 3 AI reviewers pass · branch protection requires 1 human approval.
328 Auto-merge will complete within ~30s of approval.
329 </div>
330 <div class="pr-merge-actions">
331 <button type="button" class="pr-btn-approve">Approve &amp; enable auto-merge</button>
332 <button type="button" class="pr-btn-review">Review files changed</button>
333 <button type="button" class="pr-btn-sandbox">Open sandbox</button>
334 </div>
335 </div>
336 </div>
337 </div>
338
339 {/* Sidebar */}
340 <aside class="pr-sidebar">
341 {/* Reviewers */}
342 <div class="pr-sidebar-card">
343 <h3 class="pr-sidebar-title">Reviewers</h3>
344 <div class="pr-reviewers">
345 <div class="pr-reviewer-row">
346 <span class="pr-av pr-av-agent">CL</span>
347 <span>claude[agent]</span>
348 <span class="pr-reviewer-status pr-reviewer-approved">&#10003;</span>
349 </div>
350 <div class="pr-reviewer-row">
351 <span class="pr-av pr-av-human">CC</span>
352 <span>ccantynz</span>
353 <span class="pr-reviewer-status pr-reviewer-pending">&#9679;</span>
354 </div>
355 </div>
356 </div>
357
358 {/* Labels */}
359 <div class="pr-sidebar-card">
360 <h3 class="pr-sidebar-title">Labels</h3>
361 <div class="pr-labels">
362 <span class="pr-label pr-label-indigo">ai-build</span>
363 <span class="pr-label pr-label-green">auto-repair</span>
364 <span class="pr-label pr-label-amber">flywheel</span>
365 </div>
366 </div>
367
368 {/* Branch preview */}
369 <div class="pr-sidebar-card">
370 <h3 class="pr-sidebar-title">Branch preview</h3>
371 <a href="#" class="pr-preview-link">pr-218.preview.gluecron.com</a>
372 <div class="pr-preview-meta">Sandbox booted 36 min ago · isolated · auto-expires on merge</div>
373 </div>
374
375 {/* Files changed */}
376 <div class="pr-sidebar-card">
377 <h3 class="pr-sidebar-title">Files changed</h3>
378 <div class="pr-files-list">
379 {changedFiles.map((f) => (
380 <div class="pr-file-row" key={f.path}>
381 <span class="pr-file-mark" style={`color:${f.color}`}>{f.mark}</span>
382 <span class="pr-file-path">{f.path}</span>
383 <span class="pr-file-add">+{f.add}</span>
384 <span class="pr-file-del">-{f.del}</span>
385 </div>
386 ))}
387 </div>
388 </div>
389 </aside>
390 </div>
391 </main>
392
393 {/* Footer */}
394 <footer class="pr-footer">
395 <div class="pr-footer-inner">
396 <span>&#169; 2026 gluecron</span>
397 <span class="pr-footer-sha">
398 <span class="pr-footer-dot" aria-hidden="true" />
399 82321e8 · main
400 </span>
401 </div>
402 </footer>
403
404 <script dangerouslySetInnerHTML={{ __html: inlineJs }} />
405 </div>
406 </>
407 );
408};
409
410export default PrRedesign;
411
412const inlineJs = `
413(function() {
414 var toggle = document.getElementById('pr-impact-toggle');
415 var body = document.getElementById('pr-impact-body');
416 var caret = document.getElementById('pr-impact-caret');
417 if (toggle && body && caret) {
418 toggle.addEventListener('click', function() {
419 var isOpen = body.style.display !== 'none';
420 body.style.display = isOpen ? 'none' : 'block';
421 caret.innerHTML = isOpen ? '&#9660;' : '&#9650;';
422 toggle.setAttribute('aria-expanded', String(!isOpen));
423 });
424 toggle.addEventListener('keydown', function(e) {
425 if (e.key === 'Enter' || e.key === ' ') {
426 e.preventDefault();
427 toggle.click();
428 }
429 });
430 }
431
432 // Tab switching
433 var tabs = document.querySelectorAll('.pr-tab');
434 tabs.forEach(function(tab) {
435 tab.addEventListener('click', function(e) {
436 tabs.forEach(function(t) { t.classList.remove('pr-tab-active'); });
437 tab.classList.add('pr-tab-active');
438 });
439 });
440
441 // Merge buttons feedback
442 var approveBtn = document.querySelector('.pr-btn-approve');
443 if (approveBtn) {
444 approveBtn.addEventListener('click', function() {
445 approveBtn.textContent = 'Approved — auto-merge queued';
446 approveBtn.style.background = '#1e7f5c';
447 approveBtn.disabled = true;
448 });
449 }
450
451 var sandboxBtn = document.querySelector('.pr-btn-sandbox');
452 if (sandboxBtn) {
453 sandboxBtn.addEventListener('click', function() {
454 window.open('https://pr-218.preview.gluecron.com', '_blank');
455 });
456 }
457})();
458`;
459
460const css = `
461@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;450;500;600;700&family=Inter+Tight:wght@500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap');
462
463*, *::before, *::after { box-sizing: border-box; }
464
465.pr-root {
466 font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
467 font-size: 15px;
468 line-height: 1.55;
469 letter-spacing: -0.008em;
470 color: #16181d;
471 background: #fcfcfd;
472 min-height: 100vh;
473 display: flex;
474 flex-direction: column;
475 -webkit-font-smoothing: antialiased;
476}
477
478/* Header */
479.pr-header {
480 position: sticky;
481 top: 0;
482 z-index: 100;
483 height: 56px;
484 background: rgba(252, 252, 253, 0.88);
485 backdrop-filter: saturate(160%) blur(16px);
486 border-bottom: 1px solid rgba(22, 24, 29, 0.07);
487}
488
489.pr-nav {
490 display: flex;
491 align-items: center;
492 gap: 16px;
493 max-width: 1400px;
494 margin: 0 auto;
495 height: 100%;
496 padding: 0 24px;
497}
498
499.pr-logo {
500 font-family: 'Inter Tight', sans-serif;
501 font-size: 15px;
502 font-weight: 600;
503 letter-spacing: -0.025em;
504 color: #16181d;
505 display: inline-flex;
506 align-items: center;
507 gap: 8px;
508 text-decoration: none;
509 flex-shrink: 0;
510}
511
512.pr-logo-mark {
513 width: 16px;
514 height: 16px;
515 border-radius: 5px;
516 background: #4353c9;
517 display: inline-block;
518 flex-shrink: 0;
519}
520
521.pr-search-wrap {
522 flex: 1;
523 max-width: 340px;
524 margin: 0 4px;
525 position: relative;
526}
527
528.pr-search-icon {
529 position: absolute;
530 left: 10px;
531 top: 50%;
532 transform: translateY(-50%);
533 width: 12px;
534 height: 12px;
535 border: 1.5px solid #8a8d99;
536 border-radius: 50%;
537 pointer-events: none;
538 display: block;
539}
540
541.pr-search-input {
542 width: 100%;
543 padding: 6px 12px 6px 30px;
544 background: rgba(22, 24, 29, 0.04);
545 border: 1px solid rgba(22, 24, 29, 0.12);
546 border-radius: 8px;
547 color: #16181d;
548 font-family: inherit;
549 font-size: 13px;
550 outline: none;
551 transition: border-color 0.15s;
552}
553.pr-search-input:focus {
554 border-color: #4353c9;
555 background: #fff;
556}
557
558.pr-nav-right {
559 display: flex;
560 align-items: center;
561 gap: 2px;
562 margin-left: auto;
563}
564
565.pr-nav-link {
566 color: #6b7080;
567 font-size: 13px;
568 font-weight: 500;
569 padding: 6px 10px;
570 border-radius: 6px;
571 text-decoration: none;
572 transition: color 0.15s, background 0.15s;
573}
574.pr-nav-link:hover {
575 color: #16181d;
576 background: rgba(22, 24, 29, 0.05);
577}
578
579.pr-nav-icon-btn {
580 position: relative;
581 display: inline-flex;
582 align-items: center;
583 justify-content: center;
584 width: 34px;
585 height: 34px;
586 border-radius: 6px;
587 color: #6b7080;
588 text-decoration: none;
589 flex-shrink: 0;
590 transition: color 0.15s, background 0.15s;
591}
592.pr-nav-icon-btn:hover {
593 color: #16181d;
594 background: rgba(22, 24, 29, 0.05);
595}
596
597.pr-notif-badge {
598 position: absolute;
599 top: 3px;
600 right: 3px;
601 min-width: 15px;
602 height: 15px;
603 padding: 0 4px;
604 font-size: 9.5px;
605 font-weight: 700;
606 line-height: 15px;
607 color: #fff;
608 background: #4353c9;
609 border-radius: 9999px;
610 text-align: center;
611}
612
613.pr-new-btn {
614 display: inline-flex;
615 align-items: center;
616 gap: 4px;
617 padding: 5px 12px;
618 font-size: 13px;
619 font-weight: 600;
620 border-radius: 8px;
621 background: #16181d;
622 color: #fff;
623 text-decoration: none;
624 border: 1px solid transparent;
625 line-height: 1.25;
626 transition: background 0.15s;
627}
628.pr-new-btn:hover { background: #2a2d38; }
629
630.pr-user-btn {
631 display: inline-flex;
632 align-items: center;
633 gap: 6px;
634 padding: 4px 8px 4px 4px;
635 border-radius: 8px;
636 border: 1px solid transparent;
637 background: transparent;
638 color: #16181d;
639 font-family: inherit;
640 font-size: 13px;
641 font-weight: 500;
642 cursor: pointer;
643 transition: background 0.15s, border-color 0.15s;
644}
645.pr-user-btn:hover {
646 background: rgba(22, 24, 29, 0.05);
647 border-color: rgba(22, 24, 29, 0.07);
648}
649
650.pr-user-avatar {
651 width: 28px;
652 height: 28px;
653 border-radius: 50%;
654 background: #16181d;
655 color: #fff;
656 font-size: 11px;
657 font-weight: 700;
658 display: inline-flex;
659 align-items: center;
660 justify-content: center;
661 flex-shrink: 0;
662}
663
664.pr-user-caret { color: #8a8d99; font-size: 10px; }
665
666/* Main */
667.pr-main {
668 max-width: 1400px;
669 margin: 0 auto;
670 padding: 36px 24px 80px;
671 flex: 1;
672 width: 100%;
673}
674
675/* Breadcrumb */
676.pr-breadcrumb {
677 font-size: 11px;
678 font-family: 'JetBrains Mono', monospace;
679 color: #6b7080;
680 letter-spacing: 0.10em;
681 text-transform: uppercase;
682 margin-bottom: 8px;
683}
684.pr-breadcrumb-accent {
685 color: #4353c9;
686 font-weight: 600;
687}
688
689/* Title row */
690.pr-title-row {
691 display: flex;
692 align-items: flex-start;
693 justify-content: space-between;
694 gap: 16px;
695 flex-wrap: wrap;
696 margin-bottom: 10px;
697}
698
699.pr-title {
700 font-family: 'Inter Tight', sans-serif;
701 font-size: 26px;
702 font-weight: 650;
703 letter-spacing: -0.028em;
704 line-height: 1.18;
705 color: #111318;
706 margin: 0;
707}
708
709.pr-title-number {
710 color: #8a8d99;
711 font-weight: 400;
712}
713
714.pr-title-actions {
715 display: flex;
716 gap: 8px;
717 align-items: center;
718 flex-shrink: 0;
719}
720
721.pr-state-badge {
722 display: inline-flex;
723 align-items: center;
724 gap: 4px;
725 font-size: 12px;
726 font-weight: 600;
727 padding: 4px 12px;
728 border-radius: 9999px;
729}
730
731.pr-state-open {
732 background: rgba(30, 127, 92, 0.10);
733 color: #1e7f5c;
734 border: 1px solid rgba(30, 127, 92, 0.30);
735}
736
737.pr-state-closed {
738 background: rgba(180, 35, 24, 0.08);
739 color: #b42318;
740 border: 1px solid rgba(180, 35, 24, 0.25);
741}
742
743.pr-edit-btn {
744 display: inline-flex;
745 align-items: center;
746 padding: 6px 14px;
747 border-radius: 8px;
748 font-size: 13px;
749 font-weight: 500;
750 border: 1px solid rgba(22, 24, 29, 0.12);
751 background: #ffffff;
752 color: #16181d;
753 text-decoration: none;
754 transition: background 0.15s;
755}
756.pr-edit-btn:hover { background: #f5f5f8; }
757
758/* Meta row */
759.pr-meta-row {
760 display: flex;
761 align-items: center;
762 gap: 10px;
763 flex-wrap: wrap;
764 font-size: 13px;
765 color: #6b7080;
766 margin-bottom: 20px;
767}
768
769.pr-branches {
770 display: inline-flex;
771 align-items: center;
772 gap: 6px;
773}
774
775.pr-branch-chip {
776 font-family: 'JetBrains Mono', monospace;
777 font-size: 12px;
778 padding: 2px 8px;
779 border-radius: 6px;
780 background: rgba(67, 83, 201, 0.08);
781 color: #4353c9;
782 border: 1px solid rgba(67, 83, 201, 0.22);
783}
784
785.pr-branch-arrow { color: #8a8d99; }
786
787.pr-meta-dot { color: #c4c6cf; }
788
789.pr-meta-strong {
790 color: #16181d;
791 font-weight: 600;
792}
793
794.pr-presence {
795 margin-left: auto;
796 display: inline-flex;
797 gap: 6px;
798}
799
800.pr-presence-pill {
801 display: inline-flex;
802 align-items: center;
803 gap: 5px;
804 padding: 2px 9px 2px 3px;
805 border-radius: 9999px;
806 font-size: 11px;
807 font-weight: 600;
808}
809
810.pr-presence-human {
811 background: rgba(67, 83, 201, 0.10);
812 border: 1px solid rgba(67, 83, 201, 0.25);
813 color: #4353c9;
814}
815
816.pr-presence-agent {
817 background: rgba(95, 143, 160, 0.12);
818 border: 1px solid rgba(95, 143, 160, 0.30);
819 color: #41707e;
820}
821
822.pr-presence-av {
823 width: 18px;
824 height: 18px;
825 border-radius: 50%;
826 display: inline-flex;
827 align-items: center;
828 justify-content: center;
829 font-size: 9px;
830 font-weight: 700;
831 color: #fff;
832}
833
834.pr-presence-av-human { background: #4353c9; }
835.pr-presence-av-agent { background: #5f8fa0; }
836
837/* Tabs */
838.pr-tabs {
839 display: flex;
840 gap: 1px;
841 border-bottom: 1px solid rgba(22, 24, 29, 0.07);
842 margin-bottom: 24px;
843}
844
845.pr-tab {
846 position: relative;
847 padding: 10px 14px;
848 font-size: 13px;
849 color: #6b7080;
850 font-weight: 500;
851 text-decoration: none;
852 border-bottom: 2px solid transparent;
853 margin-bottom: -1px;
854 display: inline-flex;
855 align-items: center;
856 gap: 6px;
857 transition: color 0.15s, background 0.15s;
858}
859.pr-tab:hover {
860 color: #16181d;
861 background: rgba(22, 24, 29, 0.03);
862}
863.pr-tab-active {
864 color: #16181d;
865 font-weight: 600;
866 border-bottom-color: #4353c9;
867}
868
869.pr-tab-badge {
870 font-size: 11px;
871 font-weight: 700;
872 padding: 1px 6px;
873 border-radius: 9999px;
874 background: rgba(22, 24, 29, 0.06);
875 color: #6b7080;
876}
877
878/* Grid */
879.pr-grid {
880 display: grid;
881 grid-template-columns: minmax(0, 1fr) 300px;
882 gap: 24px;
883 align-items: start;
884}
885
886.pr-main-col {
887 min-width: 0;
888 display: flex;
889 flex-direction: column;
890 gap: 16px;
891}
892
893/* Cards */
894.pr-card {
895 background: #ffffff;
896 border: 1px solid rgba(22, 24, 29, 0.07);
897 border-radius: 12px;
898 overflow: hidden;
899}
900
901.pr-card-header {
902 display: flex;
903 align-items: center;
904 gap: 8px;
905 padding: 10px 16px;
906 border-bottom: 1px solid rgba(22, 24, 29, 0.07);
907 font-size: 13px;
908 color: #6b7080;
909}
910
911.pr-av {
912 width: 22px;
913 height: 22px;
914 border-radius: 50%;
915 display: inline-flex;
916 align-items: center;
917 justify-content: center;
918 font-size: 9px;
919 font-weight: 700;
920 color: #fff;
921 flex-shrink: 0;
922}
923.pr-av-agent { background: #5f8fa0; }
924.pr-av-human { background: #4353c9; }
925
926.pr-card-author {
927 color: #16181d;
928}
929
930.pr-card-meta { color: #6b7080; }
931
932.pr-agent-badge {
933 margin-left: auto;
934 font-size: 11px;
935 font-weight: 600;
936 padding: 2px 8px;
937 border-radius: 9999px;
938 background: rgba(67, 83, 201, 0.10);
939 color: #4353c9;
940 border: 1px solid rgba(67, 83, 201, 0.30);
941}
942
943.pr-card-body {
944 padding: 16px;
945}
946
947.pr-description {
948 font-size: 14.5px;
949}
950
951.pr-description p {
952 margin: 0 0 10px;
953}
954.pr-description p:last-child { margin-bottom: 0; }
955
956.pr-desc-list {
957 margin: 0 0 10px;
958 padding-left: 22px;
959 display: flex;
960 flex-direction: column;
961 gap: 4px;
962}
963
964.pr-link {
965 color: #4353c9;
966 text-decoration: none;
967}
968.pr-link:hover { text-decoration: underline; }
969
970.pr-code {
971 font-family: 'JetBrains Mono', monospace;
972 font-size: 0.88em;
973 background: rgba(22, 24, 29, 0.04);
974 border: 1px solid rgba(22, 24, 29, 0.07);
975 padding: 1px 6px;
976 border-radius: 5px;
977 color: #16181d;
978}
979
980/* Merge Impact */
981.pr-impact-card { cursor: default; }
982
983.pr-impact-header {
984 display: flex;
985 align-items: center;
986 gap: 10px;
987 padding: 12px 16px;
988 cursor: pointer;
989 user-select: none;
990 transition: background 0.15s;
991}
992.pr-impact-header:hover { background: rgba(22, 24, 29, 0.02); }
993
994.pr-impact-risk-badge {
995 display: inline-flex;
996 align-items: center;
997 justify-content: center;
998 min-width: 34px;
999 height: 24px;
1000 border-radius: 6px;
1001 font-family: 'JetBrains Mono', monospace;
1002 font-size: 12px;
1003 font-weight: 700;
1004 background: rgba(180, 83, 9, 0.10);
1005 color: #b45309;
1006 border: 1px solid rgba(180, 83, 9, 0.25);
1007 padding: 0 8px;
1008}
1009
1010.pr-impact-title {
1011 font-size: 13.5px;
1012}
1013
1014.pr-impact-summary {
1015 font-size: 12.5px;
1016 color: #6b7080;
1017 flex: 1;
1018 white-space: nowrap;
1019 overflow: hidden;
1020 text-overflow: ellipsis;
1021}
1022
1023.pr-impact-caret {
1024 background: transparent;
1025 border: 0;
1026 color: #8a8d99;
1027 cursor: pointer;
1028 font-size: 11px;
1029 padding: 0;
1030 flex-shrink: 0;
1031}
1032
1033.pr-impact-body {
1034 border-top: 1px solid rgba(22, 24, 29, 0.07);
1035 padding: 14px 16px;
1036 display: flex;
1037 flex-direction: column;
1038 gap: 12px;
1039}
1040
1041.pr-impact-section-title {
1042 font-size: 11px;
1043 text-transform: uppercase;
1044 letter-spacing: 0.08em;
1045 color: #6b7080;
1046 margin: 0 0 6px;
1047 font-family: 'JetBrains Mono', monospace;
1048 font-weight: 600;
1049}
1050
1051.pr-impact-list {
1052 margin: 0;
1053 padding-left: 18px;
1054 font-family: 'JetBrains Mono', monospace;
1055 font-size: 12px;
1056 color: #16181d;
1057 display: flex;
1058 flex-direction: column;
1059 gap: 3px;
1060}
1061
1062/* AI Trio Review */
1063.pr-trio-card {
1064 background: #ffffff;
1065 border: 1px solid rgba(67, 83, 201, 0.28);
1066 border-radius: 12px;
1067 overflow: hidden;
1068 box-shadow: 0 0 0 1px rgba(67, 83, 201, 0.06);
1069}
1070
1071.pr-trio-header {
1072 display: flex;
1073 align-items: center;
1074 gap: 9px;
1075 padding: 12px 16px;
1076 border-bottom: 1px solid rgba(22, 24, 29, 0.07);
1077 background: linear-gradient(135deg, rgba(67, 83, 201, 0.04), rgba(95, 143, 160, 0.02));
1078}
1079
1080.pr-trio-dot {
1081 width: 8px;
1082 height: 8px;
1083 border-radius: 50%;
1084 background: #4353c9;
1085 flex-shrink: 0;
1086 display: inline-block;
1087}
1088
1089.pr-trio-title { font-size: 13.5px; }
1090
1091.pr-trio-sub {
1092 font-size: 12.5px;
1093 color: #6b7080;
1094}
1095
1096.pr-trio-grid {
1097 display: grid;
1098 grid-template-columns: repeat(3, 1fr);
1099 gap: 12px;
1100 padding: 14px 16px;
1101}
1102
1103.pr-trio-item {
1104 border: 1px solid rgba(22, 24, 29, 0.07);
1105 border-radius: 9px;
1106 padding: 12px;
1107 background: #fcfcfd;
1108}
1109
1110.pr-trio-item-header {
1111 display: flex;
1112 align-items: center;
1113 gap: 7px;
1114 margin-bottom: 8px;
1115}
1116
1117.pr-trio-item-icon {
1118 font-size: 13px;
1119 font-family: 'JetBrains Mono', monospace;
1120 font-weight: 700;
1121 color: #6b7080;
1122 width: 20px;
1123 text-align: center;
1124 flex-shrink: 0;
1125}
1126
1127.pr-trio-item-name { font-size: 13px; }
1128
1129.pr-trio-verdict {
1130 margin-left: auto;
1131 font-size: 11px;
1132 font-weight: 700;
1133 padding: 1px 8px;
1134 border-radius: 9999px;
1135}
1136
1137.pr-trio-item-body {
1138 font-size: 12.5px;
1139 color: #6b7080;
1140 margin: 0;
1141 line-height: 1.5;
1142}
1143
1144.pr-trio-disagree {
1145 display: flex;
1146 gap: 10px;
1147 padding: 10px 16px;
1148 border-top: 1px solid rgba(180, 83, 9, 0.22);
1149 background: rgba(180, 83, 9, 0.05);
1150 font-size: 12.5px;
1151 color: #16181d;
1152 align-items: flex-start;
1153}
1154
1155.pr-trio-warn-icon {
1156 color: #b45309;
1157 font-weight: 700;
1158 font-size: 13px;
1159 flex-shrink: 0;
1160 margin-top: 1px;
1161}
1162
1163/* Checks */
1164.pr-checks-card {
1165 padding: 14px 16px;
1166}
1167
1168.pr-checks-title {
1169 font-size: 11px;
1170 text-transform: uppercase;
1171 letter-spacing: 0.08em;
1172 color: #6b7080;
1173 font-family: 'JetBrains Mono', monospace;
1174 font-weight: 600;
1175 margin-bottom: 12px;
1176}
1177
1178.pr-checks-list {
1179 display: flex;
1180 flex-direction: column;
1181 gap: 10px;
1182}
1183
1184.pr-check-row {
1185 display: flex;
1186 align-items: center;
1187 gap: 10px;
1188 font-size: 13px;
1189}
1190
1191.pr-check-dot {
1192 width: 8px;
1193 height: 8px;
1194 border-radius: 50%;
1195 flex-shrink: 0;
1196 display: inline-block;
1197}
1198
1199.pr-check-name {
1200 font-family: 'JetBrains Mono', monospace;
1201 font-size: 12.5px;
1202 color: #16181d;
1203 min-width: 130px;
1204}
1205
1206.pr-check-detail {
1207 color: #6b7080;
1208 font-size: 12px;
1209 flex: 1;
1210}
1211
1212.pr-check-state {
1213 font-size: 12px;
1214 font-weight: 600;
1215 margin-left: auto;
1216 flex-shrink: 0;
1217}
1218
1219/* Merge box */
1220.pr-merge-box {
1221 background: #ffffff;
1222 border: 1px solid rgba(30, 127, 92, 0.30);
1223 border-radius: 12px;
1224 padding: 16px;
1225 display: flex;
1226 gap: 14px;
1227 align-items: flex-start;
1228}
1229
1230.pr-merge-icon {
1231 width: 34px;
1232 height: 34px;
1233 border-radius: 50%;
1234 background: rgba(30, 127, 92, 0.10);
1235 color: #1e7f5c;
1236 display: inline-flex;
1237 align-items: center;
1238 justify-content: center;
1239 font-size: 16px;
1240 flex-shrink: 0;
1241}
1242
1243.pr-merge-content { flex: 1; }
1244
1245.pr-merge-title {
1246 font-size: 14.5px;
1247 font-weight: 600;
1248 color: #16181d;
1249 margin-bottom: 2px;
1250}
1251
1252.pr-merge-desc {
1253 font-size: 13px;
1254 color: #6b7080;
1255}
1256
1257.pr-merge-actions {
1258 display: flex;
1259 gap: 8px;
1260 margin-top: 12px;
1261 flex-wrap: wrap;
1262}
1263
1264.pr-btn-approve {
1265 display: inline-flex;
1266 align-items: center;
1267 gap: 6px;
1268 padding: 7px 14px;
1269 border-radius: 8px;
1270 font-size: 13px;
1271 font-weight: 600;
1272 background: #16181d;
1273 color: #fff;
1274 border: 1px solid transparent;
1275 cursor: pointer;
1276 font-family: inherit;
1277 transition: background 0.15s;
1278}
1279.pr-btn-approve:hover:not(:disabled) { background: #2a2d38; }
1280.pr-btn-approve:disabled {
1281 background: #1e7f5c;
1282 cursor: default;
1283}
1284
1285.pr-btn-review {
1286 display: inline-flex;
1287 align-items: center;
1288 gap: 6px;
1289 padding: 7px 14px;
1290 border-radius: 8px;
1291 font-size: 13px;
1292 font-weight: 500;
1293 border: 1px solid rgba(22, 24, 29, 0.16);
1294 background: #ffffff;
1295 color: #16181d;
1296 cursor: pointer;
1297 font-family: inherit;
1298 transition: background 0.15s;
1299}
1300.pr-btn-review:hover { background: #f5f5f8; }
1301
1302.pr-btn-sandbox {
1303 display: inline-flex;
1304 align-items: center;
1305 gap: 6px;
1306 padding: 7px 14px;
1307 border-radius: 8px;
1308 font-size: 13px;
1309 font-weight: 500;
1310 border: 1px solid rgba(22, 24, 29, 0.07);
1311 background: transparent;
1312 color: #6b7080;
1313 cursor: pointer;
1314 font-family: inherit;
1315 transition: background 0.15s, color 0.15s;
1316}
1317.pr-btn-sandbox:hover {
1318 background: rgba(22, 24, 29, 0.04);
1319 color: #16181d;
1320}
1321
1322/* Sidebar */
1323.pr-sidebar {
1324 display: flex;
1325 flex-direction: column;
1326 gap: 16px;
1327}
1328
1329.pr-sidebar-card {
1330 background: #ffffff;
1331 border: 1px solid rgba(22, 24, 29, 0.07);
1332 border-radius: 12px;
1333 padding: 16px;
1334}
1335
1336.pr-sidebar-title {
1337 font-size: 11px;
1338 font-family: 'JetBrains Mono', monospace;
1339 letter-spacing: 0.12em;
1340 text-transform: uppercase;
1341 color: #6b7080;
1342 margin: 0 0 12px;
1343 font-weight: 600;
1344}
1345
1346/* Reviewers */
1347.pr-reviewers {
1348 display: flex;
1349 flex-direction: column;
1350 gap: 8px;
1351 font-size: 13px;
1352}
1353
1354.pr-reviewer-row {
1355 display: flex;
1356 align-items: center;
1357 gap: 8px;
1358}
1359
1360.pr-reviewer-status {
1361 margin-left: auto;
1362 font-size: 14px;
1363 font-weight: 700;
1364}
1365.pr-reviewer-approved { color: #1e7f5c; }
1366.pr-reviewer-pending { color: #b45309; }
1367
1368/* Labels */
1369.pr-labels {
1370 display: flex;
1371 gap: 6px;
1372 flex-wrap: wrap;
1373}
1374
1375.pr-label {
1376 font-size: 11px;
1377 font-weight: 600;
1378 padding: 2px 10px;
1379 border-radius: 9999px;
1380}
1381
1382.pr-label-indigo {
1383 background: rgba(67, 83, 201, 0.10);
1384 color: #4353c9;
1385 border: 1px solid rgba(67, 83, 201, 0.30);
1386}
1387
1388.pr-label-green {
1389 background: rgba(30, 127, 92, 0.10);
1390 color: #1e7f5c;
1391 border: 1px solid rgba(30, 127, 92, 0.28);
1392}
1393
1394.pr-label-amber {
1395 background: rgba(180, 83, 9, 0.10);
1396 color: #b45309;
1397 border: 1px solid rgba(180, 83, 9, 0.25);
1398}
1399
1400/* Branch preview */
1401.pr-preview-link {
1402 display: block;
1403 font-family: 'JetBrains Mono', monospace;
1404 font-size: 12px;
1405 color: #4353c9;
1406 text-decoration: none;
1407 margin-bottom: 8px;
1408 overflow: hidden;
1409 text-overflow: ellipsis;
1410 white-space: nowrap;
1411}
1412.pr-preview-link:hover { text-decoration: underline; }
1413
1414.pr-preview-meta {
1415 font-size: 12px;
1416 color: #6b7080;
1417}
1418
1419/* Files list */
1420.pr-files-list {
1421 display: flex;
1422 flex-direction: column;
1423 gap: 5px;
1424 font-family: 'JetBrains Mono', monospace;
1425 font-size: 11.5px;
1426}
1427
1428.pr-file-row {
1429 display: flex;
1430 align-items: center;
1431 gap: 6px;
1432 overflow: hidden;
1433}
1434
1435.pr-file-mark {
1436 font-weight: 700;
1437 flex-shrink: 0;
1438}
1439
1440.pr-file-path {
1441 overflow: hidden;
1442 text-overflow: ellipsis;
1443 white-space: nowrap;
1444 color: #16181d;
1445 flex: 1;
1446}
1447
1448.pr-file-add {
1449 margin-left: auto;
1450 color: #1e7f5c;
1451 flex-shrink: 0;
1452}
1453
1454.pr-file-del {
1455 color: #b42318;
1456 flex-shrink: 0;
1457}
1458
1459/* Footer */
1460.pr-footer {
1461 border-top: 1px solid rgba(22, 24, 29, 0.07);
1462 padding: 28px 24px;
1463 background: #fcfcfd;
1464}
1465
1466.pr-footer-inner {
1467 max-width: 1400px;
1468 margin: 0 auto;
1469 display: flex;
1470 justify-content: space-between;
1471 align-items: center;
1472 color: #8a8d99;
1473 font-size: 11px;
1474 font-family: 'JetBrains Mono', monospace;
1475 letter-spacing: 0.02em;
1476}
1477
1478.pr-footer-sha {
1479 display: inline-flex;
1480 align-items: center;
1481 gap: 6px;
1482 cursor: help;
1483}
1484
1485.pr-footer-dot {
1486 width: 6px;
1487 height: 6px;
1488 border-radius: 50%;
1489 background: #1e7f5c;
1490 opacity: 0.9;
1491 display: inline-block;
1492}
1493
1494/* Responsive */
1495@media (max-width: 900px) {
1496 .pr-grid {
1497 grid-template-columns: 1fr;
1498 }
1499 .pr-trio-grid {
1500 grid-template-columns: 1fr;
1501 }
1502 .pr-presence {
1503 margin-left: 0;
1504 margin-top: 6px;
1505 flex-basis: 100%;
1506 }
1507 .pr-meta-row {
1508 flex-wrap: wrap;
1509 }
1510}
1511`;