mergie-cli 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/LICENSE +674 -0
- package/README.md +91 -0
- package/bin/mergie-dev +18 -0
- package/bin/mergie.ts +44 -0
- package/dist/web/assets/index-4bq8mkyV.css +1 -0
- package/dist/web/assets/index-Cp_Gdwf2.js +50 -0
- package/dist/web/favicon.svg +12 -0
- package/dist/web/index.html +14 -0
- package/package.json +68 -0
- package/src/cli/args.ts +63 -0
- package/src/cli/constants.ts +32 -0
- package/src/cli/daemonClient.ts +42 -0
- package/src/cli/openBrowser.ts +11 -0
- package/src/cli/run.ts +76 -0
- package/src/daemon/aiReviewTracker.ts +71 -0
- package/src/daemon/allComments.ts +119 -0
- package/src/daemon/artifactCapture.ts +47 -0
- package/src/daemon/buildUi.ts +54 -0
- package/src/daemon/chatPrompt.ts +35 -0
- package/src/daemon/chatSocket.ts +78 -0
- package/src/daemon/createRegistry.ts +569 -0
- package/src/daemon/githubThreads.ts +92 -0
- package/src/daemon/inflight.ts +49 -0
- package/src/daemon/postMapping.ts +94 -0
- package/src/daemon/registry.ts +263 -0
- package/src/daemon/reviewPrompt.ts +22 -0
- package/src/daemon/reviewService.ts +243 -0
- package/src/daemon/router.ts +287 -0
- package/src/daemon/server.ts +106 -0
- package/src/daemon/splitView.ts +63 -0
- package/src/daemon/trpc.ts +20 -0
- package/src/db/migrate.ts +24 -0
- package/src/db/repositories/aiReviews.ts +113 -0
- package/src/db/repositories/artifacts.ts +101 -0
- package/src/db/repositories/chatSessions.ts +197 -0
- package/src/db/repositories/comments.ts +195 -0
- package/src/db/repositories/githubComments.ts +166 -0
- package/src/db/repositories/hunkViews.ts +36 -0
- package/src/db/repositories/reviewedRanges.ts +75 -0
- package/src/db/schema.ts +97 -0
- package/src/domain/config.ts +137 -0
- package/src/domain/context.ts +32 -0
- package/src/domain/diff.ts +178 -0
- package/src/domain/entities.ts +92 -0
- package/src/domain/fuzzy.ts +43 -0
- package/src/domain/generated.ts +33 -0
- package/src/domain/hash.ts +0 -0
- package/src/domain/lockfiles.ts +20 -0
- package/src/domain/paths.ts +59 -0
- package/src/domain/ranges.ts +81 -0
- package/src/domain/references.ts +36 -0
- package/src/domain/url.ts +50 -0
- package/src/domain/wordDiff.ts +174 -0
- package/src/services/ai.ts +137 -0
- package/src/services/exec.ts +44 -0
- package/src/services/ghPr.ts +102 -0
- package/src/services/ghSearch.ts +135 -0
- package/src/services/git.ts +297 -0
- package/src/services/github.ts +300 -0
- package/src/services/symbols.ts +364 -0
- package/src/web/App.tsx +32 -0
- package/src/web/components/AiReviewIndicator.tsx +63 -0
- package/src/web/components/AiReviewModal.tsx +101 -0
- package/src/web/components/AiReviewsPanel.tsx +64 -0
- package/src/web/components/ChatPanel.tsx +142 -0
- package/src/web/components/CodePreview.tsx +63 -0
- package/src/web/components/CommentComposer.tsx +40 -0
- package/src/web/components/CommentItem.tsx +59 -0
- package/src/web/components/CommentsPanel.tsx +309 -0
- package/src/web/components/CommitRail.tsx +64 -0
- package/src/web/components/ConfirmButton.tsx +32 -0
- package/src/web/components/CopyButton.tsx +33 -0
- package/src/web/components/CopyIconButton.tsx +38 -0
- package/src/web/components/DiffFrame.tsx +133 -0
- package/src/web/components/DiffLines.tsx +149 -0
- package/src/web/components/FileFrame.tsx +45 -0
- package/src/web/components/FileNavigator.tsx +195 -0
- package/src/web/components/FileTree.tsx +45 -0
- package/src/web/components/FileView.tsx +53 -0
- package/src/web/components/GithubThreadView.tsx +56 -0
- package/src/web/components/HunkCard.tsx +121 -0
- package/src/web/components/Icons.tsx +215 -0
- package/src/web/components/IdentifierMenuPortal.tsx +33 -0
- package/src/web/components/PostMenu.tsx +63 -0
- package/src/web/components/PrDescription.tsx +29 -0
- package/src/web/components/PrLoading.tsx +45 -0
- package/src/web/components/PrPicker.tsx +201 -0
- package/src/web/components/RangeSelector.tsx +141 -0
- package/src/web/components/ResultsList.tsx +159 -0
- package/src/web/components/ReviewView.tsx +308 -0
- package/src/web/components/RightRail.tsx +146 -0
- package/src/web/components/SearchRailPanel.tsx +127 -0
- package/src/web/components/Switch.tsx +31 -0
- package/src/web/components/SwitchPrModal.tsx +28 -0
- package/src/web/components/SymbolLookupMenu.tsx +35 -0
- package/src/web/components/Toolbar.tsx +79 -0
- package/src/web/components/Tooltip.tsx +72 -0
- package/src/web/index.html +13 -0
- package/src/web/lib/aiReviewIndicator.ts +29 -0
- package/src/web/lib/anchorRow.ts +25 -0
- package/src/web/lib/codeSearchFetch.ts +46 -0
- package/src/web/lib/commentFilters.ts +36 -0
- package/src/web/lib/commentVisibility.ts +90 -0
- package/src/web/lib/composerKeys.ts +24 -0
- package/src/web/lib/dedupeResults.ts +37 -0
- package/src/web/lib/diffMarks.ts +81 -0
- package/src/web/lib/fileStatus.ts +12 -0
- package/src/web/lib/filterCodeResults.ts +42 -0
- package/src/web/lib/filterPrs.ts +38 -0
- package/src/web/lib/highlight.ts +40 -0
- package/src/web/lib/identifierMenu.ts +41 -0
- package/src/web/lib/lineSelection.ts +48 -0
- package/src/web/lib/navRouting.ts +40 -0
- package/src/web/lib/navStack.ts +109 -0
- package/src/web/lib/persistedFlag.ts +43 -0
- package/src/web/lib/prPickerModel.ts +27 -0
- package/src/web/lib/railState.ts +19 -0
- package/src/web/lib/rangeCoverage.ts +27 -0
- package/src/web/lib/rangeMap.ts +42 -0
- package/src/web/lib/resultCountLabel.ts +11 -0
- package/src/web/lib/reviewProgress.ts +26 -0
- package/src/web/lib/searchInputsKey.ts +35 -0
- package/src/web/lib/searchToken.ts +25 -0
- package/src/web/lib/splitSide.ts +13 -0
- package/src/web/lib/time.ts +9 -0
- package/src/web/lib/togglePrefs.ts +81 -0
- package/src/web/lib/useEscToClose.ts +17 -0
- package/src/web/lib/useIdentifierMenu.ts +77 -0
- package/src/web/lib/useNavLookup.ts +74 -0
- package/src/web/lib/usePageTitle.ts +15 -0
- package/src/web/lib/visibleFiles.ts +52 -0
- package/src/web/main.tsx +24 -0
- package/src/web/public/favicon.svg +12 -0
- package/src/web/state/useChat.ts +181 -0
- package/src/web/state/useCodeSearch.ts +198 -0
- package/src/web/state/useReview.ts +138 -0
- package/src/web/styles.css +1020 -0
- package/src/web/trpc.ts +5 -0
- package/tsconfig.json +27 -0
- package/vite.config.ts +29 -0
|
@@ -0,0 +1,1020 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
/* Neutral gray scale (light theme) — surfaces, text, borders. */
|
|
3
|
+
--gray-50: #f8f9fb;
|
|
4
|
+
--gray-100: #f1f3f5;
|
|
5
|
+
--gray-200: #e6e8eb;
|
|
6
|
+
--gray-300: #d4d8dd;
|
|
7
|
+
--gray-400: #b0b6bd;
|
|
8
|
+
--gray-500: #868e96;
|
|
9
|
+
--gray-600: #656d76;
|
|
10
|
+
--gray-700: #464c53;
|
|
11
|
+
--gray-800: #2b3036;
|
|
12
|
+
--gray-900: #1a1d21;
|
|
13
|
+
|
|
14
|
+
/* Single accent (indigo-leaning blue). */
|
|
15
|
+
--accent: #4361ee;
|
|
16
|
+
--accent-hover: #3b55d9;
|
|
17
|
+
--accent-tint: #eaeeff;
|
|
18
|
+
--accent-border: #c7d0fb;
|
|
19
|
+
|
|
20
|
+
/* Semantic tokens. */
|
|
21
|
+
--success: #1a7f37;
|
|
22
|
+
--success-tint: #e7f5ec;
|
|
23
|
+
--success-tint-hover: #d8eede;
|
|
24
|
+
--success-border: #b7e0c4;
|
|
25
|
+
--danger: #cf222e;
|
|
26
|
+
--danger-hover: #b91c26;
|
|
27
|
+
--danger-tint: #fdecec;
|
|
28
|
+
--danger-tint-hover: #fbdcdd;
|
|
29
|
+
--danger-border: #f3c0c2;
|
|
30
|
+
--warning: #9a6700;
|
|
31
|
+
--warning-tint: #fdf3e0;
|
|
32
|
+
--warning-border: #ecd4a3;
|
|
33
|
+
|
|
34
|
+
/* Surfaces. */
|
|
35
|
+
--surface: #ffffff;
|
|
36
|
+
--surface-raised: #ffffff;
|
|
37
|
+
--surface-sunken: var(--gray-50);
|
|
38
|
+
--bg: var(--surface);
|
|
39
|
+
--bg-muted: var(--gray-50);
|
|
40
|
+
|
|
41
|
+
/* Text. */
|
|
42
|
+
--text: var(--gray-900);
|
|
43
|
+
--text-secondary: var(--gray-700);
|
|
44
|
+
--muted: var(--gray-600);
|
|
45
|
+
|
|
46
|
+
/* Diff line tints (softer/desaturated) + accent stripes. */
|
|
47
|
+
--add-bg: #eaf6ee;
|
|
48
|
+
--add-stripe: #4caf6e;
|
|
49
|
+
--del-bg: #fdecec;
|
|
50
|
+
--del-stripe: #e0707a;
|
|
51
|
+
/* Deeper tints for the exact changed words within an add/del line. */
|
|
52
|
+
--add-word-bg: #b8e8c5;
|
|
53
|
+
--del-word-bg: #f7c2c8;
|
|
54
|
+
--select-bg: #fff6d6;
|
|
55
|
+
|
|
56
|
+
/* Dividers + borders (subtler than a hard 1px gray). */
|
|
57
|
+
--border: rgba(0, 0, 0, 0.09);
|
|
58
|
+
--border-strong: rgba(0, 0, 0, 0.14);
|
|
59
|
+
|
|
60
|
+
/* Radii. */
|
|
61
|
+
--radius-control: 6px;
|
|
62
|
+
--radius-card: 10px;
|
|
63
|
+
--radius-modal: 14px;
|
|
64
|
+
--radius-pill: 999px;
|
|
65
|
+
|
|
66
|
+
/* Spacing (4px grid). */
|
|
67
|
+
--sp-1: 4px;
|
|
68
|
+
--sp-2: 8px;
|
|
69
|
+
--sp-3: 12px;
|
|
70
|
+
--sp-4: 16px;
|
|
71
|
+
--sp-6: 24px;
|
|
72
|
+
--sp-8: 32px;
|
|
73
|
+
|
|
74
|
+
/* Shadows. */
|
|
75
|
+
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06);
|
|
76
|
+
--shadow-card: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
77
|
+
--shadow-raised: 0 4px 12px rgba(0, 0, 0, 0.08), 0 1px 3px rgba(0, 0, 0, 0.06);
|
|
78
|
+
--shadow-modal: 0 16px 48px rgba(0, 0, 0, 0.18);
|
|
79
|
+
|
|
80
|
+
/* Focus ring. */
|
|
81
|
+
--ring: 0 0 0 2px var(--surface), 0 0 0 4px var(--accent);
|
|
82
|
+
|
|
83
|
+
/* Monospace stack. */
|
|
84
|
+
--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
85
|
+
|
|
86
|
+
/* Type scale. */
|
|
87
|
+
--fs-caption: 12px;
|
|
88
|
+
--fs-secondary: 13px;
|
|
89
|
+
--fs-body: 14px;
|
|
90
|
+
--fs-subhead: 16px;
|
|
91
|
+
--fs-title: 20px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
* { box-sizing: border-box; }
|
|
95
|
+
|
|
96
|
+
body {
|
|
97
|
+
margin: 0;
|
|
98
|
+
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
99
|
+
font-size: var(--fs-body);
|
|
100
|
+
line-height: 1.5;
|
|
101
|
+
color: var(--text);
|
|
102
|
+
background: var(--bg);
|
|
103
|
+
-webkit-font-smoothing: antialiased;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
code, .diff-lines, .file-name, .hunk-header code {
|
|
107
|
+
font-family: var(--mono);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* Shared visible focus ring on every interactive element. */
|
|
111
|
+
:focus-visible { outline: none; box-shadow: var(--ring); border-radius: var(--radius-control); }
|
|
112
|
+
|
|
113
|
+
/* ── Icons ─────────────────────────────────────────────────────────── */
|
|
114
|
+
.icon { display: inline-block; vertical-align: -0.125em; flex: none; }
|
|
115
|
+
|
|
116
|
+
/* ── Unified button system ─────────────────────────────────────────── */
|
|
117
|
+
.btn {
|
|
118
|
+
display: inline-flex; align-items: center; justify-content: center; gap: 6px;
|
|
119
|
+
font: inherit; font-size: var(--fs-secondary); font-weight: 500; line-height: 1;
|
|
120
|
+
padding: 6px 12px; border-radius: var(--radius-control); cursor: pointer;
|
|
121
|
+
border: 1px solid var(--border-strong); background: var(--surface); color: var(--text);
|
|
122
|
+
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease, box-shadow 0.12s ease;
|
|
123
|
+
white-space: nowrap;
|
|
124
|
+
}
|
|
125
|
+
.btn:hover:not(:disabled) { background: var(--gray-100); }
|
|
126
|
+
.btn:active:not(:disabled) { background: var(--gray-200); }
|
|
127
|
+
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
128
|
+
|
|
129
|
+
.btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
130
|
+
.btn-primary:hover:not(:disabled) { background: var(--accent-hover); border-color: var(--accent-hover); }
|
|
131
|
+
.btn-primary:active:not(:disabled) { background: var(--accent-hover); }
|
|
132
|
+
|
|
133
|
+
.btn-ghost { background: transparent; border-color: transparent; color: var(--text-secondary); }
|
|
134
|
+
.btn-ghost:hover:not(:disabled) { background: var(--gray-100); color: var(--text); }
|
|
135
|
+
|
|
136
|
+
.btn-danger { background: var(--surface); border-color: var(--danger-border); color: var(--danger); }
|
|
137
|
+
.btn-danger:hover:not(:disabled) { background: var(--danger); border-color: var(--danger); color: #fff; }
|
|
138
|
+
|
|
139
|
+
.btn-sm { padding: 3px 8px; font-size: var(--fs-caption); gap: 4px; }
|
|
140
|
+
.btn-accent { border-color: var(--accent-border); color: var(--accent); }
|
|
141
|
+
.btn-accent:hover:not(:disabled) { background: var(--accent-tint); }
|
|
142
|
+
|
|
143
|
+
/* Toggle/pressed state for any button acting as a toggle. */
|
|
144
|
+
.btn[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
145
|
+
.btn[aria-pressed="true"]:hover:not(:disabled) { background: var(--accent-hover); }
|
|
146
|
+
|
|
147
|
+
/* ── Form controls ─────────────────────────────────────────────────── */
|
|
148
|
+
.input, .select, .textarea, input[type="text"], input[type="search"], select, textarea {
|
|
149
|
+
font: inherit; font-size: var(--fs-secondary); color: var(--text);
|
|
150
|
+
background: var(--surface); border: 1px solid var(--border-strong);
|
|
151
|
+
border-radius: var(--radius-control); padding: 6px 10px;
|
|
152
|
+
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
|
153
|
+
}
|
|
154
|
+
input::placeholder, textarea::placeholder { color: var(--gray-500); }
|
|
155
|
+
.input:focus, .select:focus, .textarea:focus,
|
|
156
|
+
input[type="text"]:focus, input[type="search"]:focus, select:focus, textarea:focus {
|
|
157
|
+
outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-tint);
|
|
158
|
+
}
|
|
159
|
+
textarea { resize: vertical; }
|
|
160
|
+
|
|
161
|
+
/* Custom select chevron. */
|
|
162
|
+
select {
|
|
163
|
+
appearance: none; -webkit-appearance: none; padding-right: 28px; cursor: pointer;
|
|
164
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23656d76' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
|
165
|
+
background-repeat: no-repeat; background-position: right 8px center;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* Custom checkboxes via accent-color + sizing. */
|
|
169
|
+
input[type="checkbox"] {
|
|
170
|
+
accent-color: var(--accent); width: 15px; height: 15px; cursor: pointer; margin: 0;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* Layout */
|
|
174
|
+
.review { display: flex; flex-direction: column; height: 100vh; }
|
|
175
|
+
.review-header {
|
|
176
|
+
display: flex; flex-direction: column; gap: var(--sp-3);
|
|
177
|
+
padding: var(--sp-3) var(--sp-4); border-bottom: 1px solid var(--border);
|
|
178
|
+
background: var(--surface);
|
|
179
|
+
}
|
|
180
|
+
/* Top row: PR identity + nav links on the left, actions pinned to the right. */
|
|
181
|
+
.review-header-bar {
|
|
182
|
+
display: flex; align-items: flex-start; justify-content: space-between;
|
|
183
|
+
gap: var(--sp-4); flex-wrap: wrap;
|
|
184
|
+
}
|
|
185
|
+
.review-title { display: flex; flex-direction: column; gap: var(--sp-1); min-width: 0; }
|
|
186
|
+
.review-title-row { display: flex; align-items: baseline; gap: var(--sp-3); flex-wrap: wrap; }
|
|
187
|
+
.review-title-row strong { font-size: var(--fs-subhead); }
|
|
188
|
+
.pr-subtitle { color: var(--muted); font-size: var(--fs-secondary); }
|
|
189
|
+
.review-nav-link {
|
|
190
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
191
|
+
font-size: var(--fs-caption); color: var(--accent); text-decoration: none; font-weight: 500;
|
|
192
|
+
}
|
|
193
|
+
.review-nav-link:hover { text-decoration: underline; }
|
|
194
|
+
/* Base ← head branch line beneath the PR title, each name with a copy icon. */
|
|
195
|
+
.pr-branches { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; font-size: var(--fs-caption); }
|
|
196
|
+
.branch-name {
|
|
197
|
+
font-family: var(--font-mono, ui-monospace, monospace); font-size: var(--fs-caption);
|
|
198
|
+
color: var(--text); background: var(--gray-100); border: 1px solid var(--border);
|
|
199
|
+
border-radius: var(--radius-control); padding: 1px 6px;
|
|
200
|
+
}
|
|
201
|
+
.branch-arrow { color: var(--muted); padding: 0 2px; }
|
|
202
|
+
/* Small ghost icon button used for inline copy-to-clipboard controls. */
|
|
203
|
+
.icon-copy-btn {
|
|
204
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
205
|
+
border: 1px solid transparent; border-radius: var(--radius-control);
|
|
206
|
+
background: transparent; color: var(--muted); cursor: pointer; padding: 3px;
|
|
207
|
+
transition: background 0.12s ease, color 0.12s ease; flex: none;
|
|
208
|
+
}
|
|
209
|
+
.icon-copy-btn:hover, .icon-copy-btn:focus-visible { background: var(--gray-100); color: var(--text); }
|
|
210
|
+
/* Keep the "Open on GitHub" link and its copy button as a vertically-centered
|
|
211
|
+
unit, so the icon-only button lines up with the link text rather than the
|
|
212
|
+
(much taller) title line the row is baseline-aligned to. */
|
|
213
|
+
.pr-link-group { display: inline-flex; align-items: center; gap: 4px; }
|
|
214
|
+
.review-actions { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
|
|
215
|
+
.review-action-group { display: flex; align-items: center; gap: var(--sp-2); }
|
|
216
|
+
.review-action-divider { width: 1px; height: 22px; background: var(--border); }
|
|
217
|
+
|
|
218
|
+
/* PR description body — rendered inside the right-rail description panel. */
|
|
219
|
+
.pr-description-body { padding: var(--sp-3) var(--sp-4); }
|
|
220
|
+
.pr-description-body h1 { font-size: var(--fs-subhead); margin: var(--sp-3) 0 var(--sp-2); }
|
|
221
|
+
.pr-description-body h2 { font-size: var(--fs-body); margin: var(--sp-3) 0 var(--sp-2); }
|
|
222
|
+
.pr-description-body h3, .pr-description-body h4 { font-size: var(--fs-secondary); margin: var(--sp-2) 0; }
|
|
223
|
+
.review-body { display: flex; flex: 1; min-height: 0; }
|
|
224
|
+
.sidebar {
|
|
225
|
+
width: 320px; flex: none; border-right: 1px solid var(--border);
|
|
226
|
+
display: flex; flex-direction: column; overflow: hidden;
|
|
227
|
+
background: var(--surface-sunken);
|
|
228
|
+
transition: width 0.15s ease;
|
|
229
|
+
}
|
|
230
|
+
/* Collapsed: a slim strip pinned at the left edge holding only the expand
|
|
231
|
+
button; the diff area widens to fill the reclaimed space. */
|
|
232
|
+
.sidebar.collapsed { width: 30px; }
|
|
233
|
+
/* Top row: the "View" heading and the collapse/expand chevron share one line,
|
|
234
|
+
so there is no empty band above the switches. */
|
|
235
|
+
.sidebar-header {
|
|
236
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
237
|
+
padding: var(--sp-3) var(--sp-3) var(--sp-2);
|
|
238
|
+
}
|
|
239
|
+
.sidebar-heading {
|
|
240
|
+
margin: 0; padding: 0 var(--sp-1);
|
|
241
|
+
font-size: var(--fs-caption); font-weight: 600; text-transform: uppercase;
|
|
242
|
+
letter-spacing: 0.05em; color: var(--muted);
|
|
243
|
+
}
|
|
244
|
+
/* Collapsed strip: hide the heading, centre the (expand) chevron. */
|
|
245
|
+
.sidebar.collapsed .sidebar-header { justify-content: center; padding: var(--sp-2) 0 0; }
|
|
246
|
+
.sidebar.collapsed .sidebar-heading,
|
|
247
|
+
.sidebar.collapsed .review-ring-group { display: none; }
|
|
248
|
+
.sidebar-toggle {
|
|
249
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
250
|
+
width: 24px; height: 24px; padding: 0; border: none; border-radius: var(--radius-control);
|
|
251
|
+
background: transparent; color: var(--muted); cursor: pointer;
|
|
252
|
+
transition: background 0.12s ease, color 0.12s ease;
|
|
253
|
+
}
|
|
254
|
+
.sidebar-toggle:hover, .sidebar-toggle:focus-visible { background: var(--gray-200); color: var(--text); }
|
|
255
|
+
/* Hide the switches/filter/file-tree when collapsed without unmounting them
|
|
256
|
+
(so their state — filter text, switch positions — is preserved). */
|
|
257
|
+
.sidebar-content { display: flex; flex-direction: column; flex: 1; min-height: 0; overflow: hidden; }
|
|
258
|
+
.sidebar.collapsed .sidebar-content { display: none; }
|
|
259
|
+
.diff-area { flex: 1; overflow: auto; padding: 0 var(--sp-4) var(--sp-4); }
|
|
260
|
+
|
|
261
|
+
/* Toolbar — the "View" group of visibility toggle switches. Sits directly under
|
|
262
|
+
the sidebar header row, so it carries no top padding of its own. */
|
|
263
|
+
.toolbar {
|
|
264
|
+
display: flex; flex-direction: column; gap: var(--sp-1);
|
|
265
|
+
padding: 0 var(--sp-3) var(--sp-3); border-bottom: 1px solid var(--border); font-size: var(--fs-secondary);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/* Switch: a label on the left + an iOS-style on/off pill on the right. */
|
|
269
|
+
.switch-row {
|
|
270
|
+
display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
|
|
271
|
+
cursor: pointer; padding: 4px var(--sp-1); border-radius: var(--radius-control);
|
|
272
|
+
color: var(--text-secondary);
|
|
273
|
+
}
|
|
274
|
+
.switch-row:hover { background: var(--gray-100); color: var(--text); }
|
|
275
|
+
.switch-label { user-select: none; }
|
|
276
|
+
/* The native checkbox is visually hidden but stays in the layout/focus order. */
|
|
277
|
+
.switch-input {
|
|
278
|
+
position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
|
|
279
|
+
border: 0; clip: rect(0 0 0 0); overflow: hidden; white-space: nowrap;
|
|
280
|
+
}
|
|
281
|
+
.switch-track {
|
|
282
|
+
flex: none; position: relative; width: 34px; height: 20px; border-radius: var(--radius-pill);
|
|
283
|
+
background: var(--gray-300); transition: background 0.12s ease;
|
|
284
|
+
}
|
|
285
|
+
.switch-knob {
|
|
286
|
+
position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%;
|
|
287
|
+
background: var(--surface); box-shadow: var(--shadow-sm); transition: transform 0.12s ease;
|
|
288
|
+
}
|
|
289
|
+
.switch-input:checked + .switch-track { background: var(--accent); }
|
|
290
|
+
.switch-input:checked + .switch-track .switch-knob { transform: translateX(14px); }
|
|
291
|
+
/* Project the shared focus ring onto the track when the input is keyboard-focused. */
|
|
292
|
+
.switch-input:focus-visible + .switch-track { box-shadow: var(--ring); }
|
|
293
|
+
|
|
294
|
+
/* The whitespace switch is wrapped in a hover tooltip; make that wrapper a
|
|
295
|
+
full-width block so the switch row's label + pill still justify across the
|
|
296
|
+
sidebar (an inline wrapper would collapse to content width and mis-align it). */
|
|
297
|
+
.tooltip-wrap.tooltip-block { display: block; }
|
|
298
|
+
|
|
299
|
+
/* "More filters" — the two less-used View filters live in a popover so the
|
|
300
|
+
group stays compact. The button carries a count badge when any are active. */
|
|
301
|
+
.more-filters { position: relative; }
|
|
302
|
+
.more-filters-btn {
|
|
303
|
+
display: flex; align-items: center; gap: var(--sp-2); width: 100%;
|
|
304
|
+
padding: 4px var(--sp-1); border: none; background: transparent; cursor: pointer;
|
|
305
|
+
color: var(--text-secondary); font: inherit; font-size: var(--fs-secondary);
|
|
306
|
+
border-radius: var(--radius-control);
|
|
307
|
+
}
|
|
308
|
+
.more-filters-btn:hover { background: var(--gray-100); color: var(--text); }
|
|
309
|
+
.more-filters-btn > span:first-child { flex: 1; text-align: left; }
|
|
310
|
+
.more-filters-btn svg { color: var(--muted); }
|
|
311
|
+
.more-filters-badge {
|
|
312
|
+
background: var(--accent); color: #fff; font-size: 11px; font-weight: 600;
|
|
313
|
+
min-width: 16px; text-align: center; padding: 0 5px; border-radius: var(--radius-pill);
|
|
314
|
+
}
|
|
315
|
+
.more-filters-pop {
|
|
316
|
+
position: absolute; z-index: 5; top: calc(100% + 4px); left: 0; right: 0;
|
|
317
|
+
background: var(--surface); border: 1px solid var(--border-strong);
|
|
318
|
+
border-radius: var(--radius-card); box-shadow: var(--shadow-raised); padding: var(--sp-1);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/* Review progress — replaces the "View" section heading with a ring gauge whose
|
|
322
|
+
fill grows as hunks are reviewed, wrapping the count of hunks still *left*.
|
|
323
|
+
Counts viewed hunks against all hunks in the on-screen range; at 100% it turns
|
|
324
|
+
green with a check. */
|
|
325
|
+
.review-ring-group { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
|
|
326
|
+
.review-ring { position: relative; width: 34px; height: 34px; flex: none; }
|
|
327
|
+
.review-ring svg { position: absolute; inset: 0; transform: rotate(-90deg); }
|
|
328
|
+
.review-ring-track { fill: none; stroke: var(--gray-200); }
|
|
329
|
+
.review-ring-fill {
|
|
330
|
+
fill: none; stroke: var(--accent); stroke-linecap: round;
|
|
331
|
+
transition: stroke-dashoffset 0.25s ease, stroke 0.2s ease;
|
|
332
|
+
}
|
|
333
|
+
.review-ring.done .review-ring-fill { stroke: var(--success); }
|
|
334
|
+
.review-ring-num {
|
|
335
|
+
position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
|
|
336
|
+
font-size: var(--fs-secondary); font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums;
|
|
337
|
+
}
|
|
338
|
+
.review-ring.done .review-ring-num { color: var(--success); }
|
|
339
|
+
.review-ring-caption { font-size: var(--fs-caption); color: var(--muted); line-height: 1.2; }
|
|
340
|
+
|
|
341
|
+
/* Range selector — summary chip + popover panel with commit rail */
|
|
342
|
+
.range-selector { position: relative; font-size: var(--fs-secondary); }
|
|
343
|
+
.range-chip {
|
|
344
|
+
display: inline-flex; align-items: center; gap: 6px; cursor: pointer; max-width: 420px;
|
|
345
|
+
border: 1px solid var(--border-strong); background: var(--surface); border-radius: var(--radius-pill);
|
|
346
|
+
padding: 5px 12px; font-size: var(--fs-caption); color: var(--text);
|
|
347
|
+
}
|
|
348
|
+
.range-chip:hover { background: var(--gray-100); }
|
|
349
|
+
.range-chip-coverage { font-weight: 600; white-space: nowrap; flex: none; }
|
|
350
|
+
.range-chip-sep { color: var(--muted); flex: none; }
|
|
351
|
+
.range-chip-subject { color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
352
|
+
.chip-caret { color: var(--muted); display: inline-flex; flex: none; }
|
|
353
|
+
|
|
354
|
+
.range-panel {
|
|
355
|
+
position: absolute; right: 0; top: 38px; z-index: 20; width: 480px; max-width: 84vw;
|
|
356
|
+
background: var(--surface-raised); border: 1px solid var(--border); border-radius: var(--radius-card);
|
|
357
|
+
box-shadow: var(--shadow-modal); padding: var(--sp-3);
|
|
358
|
+
}
|
|
359
|
+
.range-panel-actions { display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-3); }
|
|
360
|
+
.range-caption { margin: var(--sp-3) 0 0; font-size: var(--fs-caption); color: var(--muted); line-height: 1.5; }
|
|
361
|
+
.range-caption code { background: var(--gray-100); padding: 1px 4px; border-radius: 4px; }
|
|
362
|
+
/* Truncate long commit subjects in the caption so the panel keeps its width. */
|
|
363
|
+
.range-caption-subject {
|
|
364
|
+
color: var(--text-secondary); font-style: italic;
|
|
365
|
+
display: inline-block; max-width: 240px; overflow: hidden; text-overflow: ellipsis;
|
|
366
|
+
white-space: nowrap; vertical-align: bottom;
|
|
367
|
+
}
|
|
368
|
+
.reviewed-count { color: var(--muted); font-size: var(--fs-caption); }
|
|
369
|
+
.range-reviewed-badge {
|
|
370
|
+
display: inline-flex; align-items: center; gap: 5px;
|
|
371
|
+
color: var(--success); font-weight: 600; font-size: var(--fs-secondary);
|
|
372
|
+
background: var(--success-tint); border: 1px solid var(--success-border);
|
|
373
|
+
border-radius: var(--radius-control); padding: 5px 10px; cursor: pointer;
|
|
374
|
+
}
|
|
375
|
+
.range-reviewed-badge:hover { background: var(--success-tint-hover); }
|
|
376
|
+
|
|
377
|
+
/* Commit rail */
|
|
378
|
+
.rail-hint { margin: 0 0 var(--sp-2); font-size: var(--fs-caption); color: var(--muted); }
|
|
379
|
+
.rail-base { font-size: var(--fs-caption); color: var(--muted); padding: 2px 8px; }
|
|
380
|
+
.rail-list {
|
|
381
|
+
list-style: none; margin: 0; padding: 0; max-height: 320px; overflow: auto;
|
|
382
|
+
border-left: 2px solid var(--border);
|
|
383
|
+
}
|
|
384
|
+
.rail-row {
|
|
385
|
+
display: flex; align-items: center; gap: var(--sp-2); width: 100%; text-align: left;
|
|
386
|
+
border: none; background: none; cursor: pointer; padding: 6px 8px; font-size: var(--fs-caption);
|
|
387
|
+
border-left: 3px solid transparent; margin-left: -2px; border-radius: 0 var(--radius-control) var(--radius-control) 0;
|
|
388
|
+
}
|
|
389
|
+
.rail-row:hover { background: var(--gray-100); }
|
|
390
|
+
.rail-row.in-range { background: var(--accent-tint); border-left-color: var(--accent); }
|
|
391
|
+
.rail-row.range-start, .rail-row.range-end { font-weight: 600; }
|
|
392
|
+
.rail-sha { color: var(--accent); }
|
|
393
|
+
.rail-subject { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
394
|
+
.rail-time { color: var(--muted); font-variant-numeric: tabular-nums; }
|
|
395
|
+
|
|
396
|
+
/* File tree */
|
|
397
|
+
.file-tree { overflow: auto; flex: 1; }
|
|
398
|
+
.file-search-wrap { position: relative; margin: var(--sp-3); }
|
|
399
|
+
.file-search-wrap .icon {
|
|
400
|
+
position: absolute; left: 9px; top: 50%; transform: translateY(-50%);
|
|
401
|
+
color: var(--gray-500); pointer-events: none;
|
|
402
|
+
}
|
|
403
|
+
/* Higher-specificity selector so the icon-clearing left padding wins over the
|
|
404
|
+
base input padding shorthand (input[type="search"], 0,1,1). */
|
|
405
|
+
.file-search-wrap .file-search { width: 100%; padding: 6px 10px 6px 30px; }
|
|
406
|
+
.file-tree ul { list-style: none; margin: 0; padding: 0 var(--sp-2) var(--sp-2); }
|
|
407
|
+
.file-item a {
|
|
408
|
+
display: flex; align-items: center; gap: var(--sp-2); padding: 6px 8px; border-radius: var(--radius-control);
|
|
409
|
+
text-decoration: none; color: var(--text); font-size: var(--fs-caption);
|
|
410
|
+
}
|
|
411
|
+
.file-item a:hover { background: var(--gray-100); }
|
|
412
|
+
.file-item.viewed a { color: var(--muted); }
|
|
413
|
+
.file-flag { width: 14px; display: inline-flex; color: var(--success); flex: none; }
|
|
414
|
+
.file-status-icon { display: inline-flex; flex: none; }
|
|
415
|
+
.file-status-icon.added { color: var(--success); }
|
|
416
|
+
.file-status-icon.modified { color: var(--warning); }
|
|
417
|
+
.file-status-icon.deleted { color: var(--danger); }
|
|
418
|
+
.file-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
419
|
+
.file-hunkcount {
|
|
420
|
+
color: var(--muted); font-size: 11px; background: var(--gray-100);
|
|
421
|
+
border-radius: var(--radius-pill); padding: 1px 7px; min-width: 20px; text-align: center;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/* File sections + hunks */
|
|
425
|
+
.file-section { margin-bottom: var(--sp-8); }
|
|
426
|
+
/* Height is coupled to `.hunk-header { top: … }` below — the hunk header pins
|
|
427
|
+
directly beneath the file heading, so the two values must stay equal. */
|
|
428
|
+
.file-heading {
|
|
429
|
+
font-size: var(--fs-body); font-weight: 600; height: 32px; box-sizing: border-box; margin: 0; padding: 0 var(--sp-2);
|
|
430
|
+
display: flex; align-items: center; gap: var(--sp-2);
|
|
431
|
+
border-bottom: 1px solid var(--border); background: var(--surface);
|
|
432
|
+
position: sticky; top: 0; z-index: 3;
|
|
433
|
+
}
|
|
434
|
+
.file-heading small { color: var(--muted); font-weight: normal; font-size: var(--fs-caption); }
|
|
435
|
+
/* Group the file-heading actions so only their button padding separates them —
|
|
436
|
+
the flex gap would otherwise double the space between two ghost buttons. */
|
|
437
|
+
.file-heading-actions { display: inline-flex; align-items: center; gap: 0; }
|
|
438
|
+
/* Clip the card to its rounded shape with `clip-path` rather than
|
|
439
|
+
`overflow: hidden`. An overflow clip on a sticky element's scroll-ancestor
|
|
440
|
+
breaks `position: sticky` (the hunk header would stop pinning), but
|
|
441
|
+
`clip-path` only clips painting — it leaves sticky positioning intact. This
|
|
442
|
+
clips every descendant (change tint + the left accent stripe, both of which
|
|
443
|
+
have square corners) to the card's radius, so no change-color leaks at any
|
|
444
|
+
corner. The header keeps SQUARE corners so that, while pinned mid-scroll, it
|
|
445
|
+
fully occludes the diff rows behind it (a rounded header corner would be
|
|
446
|
+
transparent and reveal a sliver of the row's tint underneath). */
|
|
447
|
+
.hunk-card {
|
|
448
|
+
border: 1px solid var(--border); border-radius: var(--radius-card); margin: var(--sp-3) 0;
|
|
449
|
+
background: var(--surface); box-shadow: var(--shadow-card);
|
|
450
|
+
clip-path: inset(0 round var(--radius-card));
|
|
451
|
+
}
|
|
452
|
+
.hunk-card.viewed { background: var(--surface-sunken); }
|
|
453
|
+
.hunk-card.viewed .hunk-header { background: var(--surface-sunken); }
|
|
454
|
+
.hunk-header {
|
|
455
|
+
display: flex; align-items: center; gap: var(--sp-2);
|
|
456
|
+
background: var(--surface-sunken); padding: 6px 10px; font-size: var(--fs-caption);
|
|
457
|
+
border-bottom: 1px solid var(--border);
|
|
458
|
+
/* `top` must equal `.file-heading` height so this pins flush beneath it. */
|
|
459
|
+
position: sticky; top: 32px; z-index: 2;
|
|
460
|
+
}
|
|
461
|
+
.hunk-collapse {
|
|
462
|
+
border: none; background: none; cursor: pointer; color: var(--muted); padding: 2px;
|
|
463
|
+
display: inline-flex; align-items: center; border-radius: 4px;
|
|
464
|
+
}
|
|
465
|
+
.hunk-collapse:hover { background: var(--gray-200); color: var(--text); }
|
|
466
|
+
.hunk-title { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); font-family: var(--mono); }
|
|
467
|
+
|
|
468
|
+
/* Hunk-header actions: icon-only. Labels are shown as a near-instant tooltip
|
|
469
|
+
(see .tooltip below), not an expanding pill. */
|
|
470
|
+
.hunk-actions { display: inline-flex; align-items: center; gap: 2px; flex: none; }
|
|
471
|
+
.hunk-action {
|
|
472
|
+
position: relative; display: inline-flex; align-items: center; justify-content: center;
|
|
473
|
+
border: 1px solid transparent; border-radius: var(--radius-control);
|
|
474
|
+
background: transparent; color: var(--text-secondary); cursor: pointer;
|
|
475
|
+
padding: 4px; font-size: var(--fs-caption);
|
|
476
|
+
transition: background 0.12s ease, color 0.12s ease;
|
|
477
|
+
}
|
|
478
|
+
.hunk-action:hover, .hunk-action:focus-visible { background: var(--gray-100); color: var(--text); }
|
|
479
|
+
.hunk-viewed {
|
|
480
|
+
display: inline-flex; align-items: center; gap: 5px; cursor: pointer; white-space: nowrap;
|
|
481
|
+
font-size: var(--fs-caption); color: var(--text-secondary);
|
|
482
|
+
}
|
|
483
|
+
.hunk-card.viewed .hunk-viewed { color: var(--success); font-weight: 500; }
|
|
484
|
+
|
|
485
|
+
/* Diff lines */
|
|
486
|
+
.diff-lines { border-collapse: collapse; width: 100%; font-size: 13px; line-height: 1.55; }
|
|
487
|
+
.diff-lines td { padding: 1px 8px; vertical-align: top; white-space: pre-wrap; }
|
|
488
|
+
.diff-lines .gutter { color: var(--gray-500); text-align: right; user-select: none; width: 1%; padding: 1px 6px; }
|
|
489
|
+
.diff-lines .marker { user-select: none; width: 1ch; padding-left: 4px; padding-right: 0; color: var(--gray-500); }
|
|
490
|
+
.line-add { background: var(--add-bg); box-shadow: inset 3px 0 0 var(--add-stripe); }
|
|
491
|
+
.line-add .marker { color: var(--success); }
|
|
492
|
+
.line-del { background: var(--del-bg); box-shadow: inset 3px 0 0 var(--del-stripe); }
|
|
493
|
+
.line-del .marker { color: var(--danger); }
|
|
494
|
+
.line.selected { background: var(--select-bg); }
|
|
495
|
+
.diff-lines.dragging { user-select: none; cursor: crosshair; }
|
|
496
|
+
|
|
497
|
+
/* Word-level (intra-line) change highlight, layered over the line background. */
|
|
498
|
+
mark.diff-word { background: none; color: inherit; border-radius: 2px; }
|
|
499
|
+
.line-add mark.diff-word, .split-add mark.diff-word { background: var(--add-word-bg); }
|
|
500
|
+
.line-del mark.diff-word, .split-del mark.diff-word { background: var(--del-word-bg); }
|
|
501
|
+
|
|
502
|
+
/* Comments */
|
|
503
|
+
.line-act { width: 22px; padding: 0; text-align: center; }
|
|
504
|
+
.add-comment {
|
|
505
|
+
opacity: 0; border: none; background: var(--accent); color: #fff; cursor: pointer;
|
|
506
|
+
border-radius: 5px; width: 17px; height: 17px; line-height: 1; font-size: 13px; padding: 0;
|
|
507
|
+
transition: opacity 0.1s ease, transform 0.1s ease;
|
|
508
|
+
}
|
|
509
|
+
.add-comment:hover { transform: scale(1.1); }
|
|
510
|
+
.line:hover .add-comment { opacity: 1; }
|
|
511
|
+
/* Comment rows / hunk-level comments sit ABOVE the sticky hunk header
|
|
512
|
+
(z-index 2) so an open composer, edit field, post popover, or an existing
|
|
513
|
+
comment's meta row is never painted over when scrolled mid-hunk. */
|
|
514
|
+
.comment-row { position: relative; z-index: 3; scroll-margin-top: 88px; }
|
|
515
|
+
.comment-row td { background: var(--surface-sunken); padding: var(--sp-2) var(--sp-3) !important; }
|
|
516
|
+
.comment-item {
|
|
517
|
+
border: 1px solid var(--border); border-radius: var(--radius-card); background: var(--surface);
|
|
518
|
+
padding: var(--sp-2) var(--sp-3); margin: var(--sp-1) 0; box-shadow: var(--shadow-sm);
|
|
519
|
+
}
|
|
520
|
+
.hunk-level-comment { position: relative; z-index: 3; padding: var(--sp-2) var(--sp-3); background: var(--surface); }
|
|
521
|
+
.comment-meta { display: flex; align-items: center; gap: var(--sp-2); font-size: 11px; color: var(--muted); margin-bottom: var(--sp-1); flex-wrap: wrap; }
|
|
522
|
+
.comment-tools { margin-left: auto; display: flex; gap: 4px; align-items: center; }
|
|
523
|
+
.comment-body { font-size: var(--fs-secondary); }
|
|
524
|
+
.comment-body p { margin: var(--sp-1) 0; }
|
|
525
|
+
.comment-body pre {
|
|
526
|
+
background: var(--surface-sunken); padding: var(--sp-2) var(--sp-3); border-radius: var(--radius-control);
|
|
527
|
+
overflow-x: auto; border: 1px solid var(--border); font-size: var(--fs-caption);
|
|
528
|
+
}
|
|
529
|
+
.comment-body pre code { white-space: pre; }
|
|
530
|
+
.comment-body code:not(pre code) { background: var(--gray-100); padding: 1px 5px; border-radius: 4px; font-size: 0.92em; }
|
|
531
|
+
.comment-composer { display: flex; flex-direction: column; gap: var(--sp-2); }
|
|
532
|
+
.comment-textarea { width: 100%; min-height: 72px; font-family: inherit; }
|
|
533
|
+
.comment-actions { display: flex; gap: var(--sp-2); }
|
|
534
|
+
|
|
535
|
+
.notice { color: var(--muted); font-style: italic; font-size: var(--fs-secondary); }
|
|
536
|
+
|
|
537
|
+
.file-heading-name { flex: 1; overflow: hidden; text-overflow: ellipsis; }
|
|
538
|
+
|
|
539
|
+
/* Modal + full-file split view */
|
|
540
|
+
.modal-overlay {
|
|
541
|
+
position: fixed; inset: 0; background: rgba(23, 25, 28, 0.45); backdrop-filter: blur(1px);
|
|
542
|
+
display: flex; align-items: center; justify-content: center; z-index: 100;
|
|
543
|
+
}
|
|
544
|
+
.modal {
|
|
545
|
+
background: var(--surface); border-radius: var(--radius-modal); box-shadow: var(--shadow-modal);
|
|
546
|
+
display: flex; flex-direction: column; overflow: hidden;
|
|
547
|
+
}
|
|
548
|
+
.modal.full-file { width: 94vw; height: 90vh; }
|
|
549
|
+
.modal-header {
|
|
550
|
+
display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-3) var(--sp-4);
|
|
551
|
+
border-bottom: 1px solid var(--border); background: var(--surface);
|
|
552
|
+
}
|
|
553
|
+
.modal-header strong { font-size: var(--fs-subhead); }
|
|
554
|
+
.modal-title-strong { display: inline-flex; align-items: center; gap: 6px; }
|
|
555
|
+
.modal-header-spacer { flex: 1; }
|
|
556
|
+
.modal-title {
|
|
557
|
+
flex: 1; min-width: 0; display: flex; align-items: center; gap: var(--sp-2);
|
|
558
|
+
font-family: var(--mono); font-size: var(--fs-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
559
|
+
}
|
|
560
|
+
.modal-title .icon { color: var(--muted); }
|
|
561
|
+
.modal-close {
|
|
562
|
+
border: none; background: none; cursor: pointer; color: var(--muted);
|
|
563
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
564
|
+
width: 30px; height: 30px; border-radius: var(--radius-control); flex: none;
|
|
565
|
+
}
|
|
566
|
+
.modal-close:hover { background: var(--gray-100); color: var(--text); }
|
|
567
|
+
.split-base-head {
|
|
568
|
+
display: inline-flex; align-items: center; gap: 6px; font-size: var(--fs-caption);
|
|
569
|
+
color: var(--muted); font-family: var(--mono);
|
|
570
|
+
}
|
|
571
|
+
.split-base-head code { background: var(--gray-100); padding: 1px 5px; border-radius: 4px; }
|
|
572
|
+
.split-labels { display: flex; gap: 0; color: var(--muted); font-size: var(--fs-caption); border-bottom: 1px solid var(--border); background: var(--surface-sunken); }
|
|
573
|
+
.split-labels span { flex: 1; text-align: center; padding: 5px 0; font-weight: 500; }
|
|
574
|
+
.split-labels span:first-child { border-right: 1px solid var(--border); }
|
|
575
|
+
/* The navigator's code-frame wrapper must be a constrained flex child so the
|
|
576
|
+
file/diff body inside it scrolls instead of overflowing the fixed-height
|
|
577
|
+
modal (which clips via overflow:hidden). */
|
|
578
|
+
.nav-code { flex: 1; min-height: 0; display: flex; flex-direction: column; }
|
|
579
|
+
/* Non-current navigator frames stay mounted (so their scroll survives
|
|
580
|
+
Back/Forward) but are hidden. */
|
|
581
|
+
.nav-slot { flex: 1; min-height: 0; display: flex; flex-direction: column; }
|
|
582
|
+
.nav-code.hidden, .nav-slot.hidden { display: none; }
|
|
583
|
+
.full-file-body { overflow: auto; flex: 1; }
|
|
584
|
+
.split-table { border-collapse: collapse; width: 100%; font-size: 13px; line-height: 1.55; table-layout: fixed; }
|
|
585
|
+
.split-table td { padding: 1px 8px; vertical-align: top; white-space: pre-wrap; word-break: break-all; }
|
|
586
|
+
.split-table td.split-code:first-of-type, .split-no:first-child { border-right: 1px solid var(--border); }
|
|
587
|
+
.split-no { width: 48px; text-align: right; color: var(--gray-500); user-select: none; }
|
|
588
|
+
.split-code { font-family: var(--mono); }
|
|
589
|
+
.split-del { background: var(--del-bg); box-shadow: inset 3px 0 0 var(--del-stripe); }
|
|
590
|
+
.split-add { background: var(--add-bg); box-shadow: inset 3px 0 0 var(--add-stripe); }
|
|
591
|
+
.split-empty { background: var(--surface-sunken); }
|
|
592
|
+
/* One-shot highlight of the anchored change on open, then fade out (mirrors
|
|
593
|
+
the in-diff comment-flash). Leaves no persistent box once faded. */
|
|
594
|
+
@keyframes split-anchor-fade {
|
|
595
|
+
from { box-shadow: inset 0 0 0 2px var(--accent); background: var(--accent-tint); }
|
|
596
|
+
to { box-shadow: inset 0 0 0 2px transparent; background: transparent; }
|
|
597
|
+
}
|
|
598
|
+
.split-anchor td { animation: split-anchor-fade 1.1s ease-out; }
|
|
599
|
+
/* Centered placeholder for an empty base/head (added/deleted file). */
|
|
600
|
+
.modal-empty-side {
|
|
601
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--sp-2);
|
|
602
|
+
min-height: 240px; color: var(--muted); text-align: center; padding: var(--sp-8);
|
|
603
|
+
}
|
|
604
|
+
.modal-empty-side .icon { color: var(--gray-400); }
|
|
605
|
+
|
|
606
|
+
/* All-comments view */
|
|
607
|
+
.all-comments {
|
|
608
|
+
padding: var(--sp-6); max-width: 900px; margin: 0 auto;
|
|
609
|
+
display: flex; flex-direction: column; gap: var(--sp-4);
|
|
610
|
+
}
|
|
611
|
+
.all-comments-header { display: flex; flex-direction: column; gap: var(--sp-2); }
|
|
612
|
+
.all-comments-header h1 { font-size: var(--fs-title); margin: 0; }
|
|
613
|
+
.all-comments-header a { display: inline-flex; align-items: center; gap: 4px; font-size: var(--fs-caption); color: var(--accent); text-decoration: none; align-self: flex-start; }
|
|
614
|
+
.all-comments-header a:hover { text-decoration: underline; }
|
|
615
|
+
.comment-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
|
|
616
|
+
.comment-list-item {
|
|
617
|
+
border: 1px solid var(--border); border-radius: var(--radius-card); padding: var(--sp-3) var(--sp-4);
|
|
618
|
+
background: var(--surface); box-shadow: var(--shadow-card);
|
|
619
|
+
}
|
|
620
|
+
.comment-list-meta { display: flex; align-items: center; gap: var(--sp-3); font-size: var(--fs-caption); color: var(--muted); flex-wrap: wrap; }
|
|
621
|
+
.comment-list-body { margin: var(--sp-2) 0; font-size: var(--fs-secondary); }
|
|
622
|
+
.comment-list-actions { display: flex; gap: var(--sp-2); align-items: center; font-size: var(--fs-caption); }
|
|
623
|
+
.comment-edit { margin: var(--sp-2) 0; }
|
|
624
|
+
.comment-edit .comment-textarea { width: 100%; min-height: 64px; }
|
|
625
|
+
.comment-edit-actions { display: flex; gap: var(--sp-2); align-items: center; font-size: var(--fs-caption); margin-top: var(--sp-1); }
|
|
626
|
+
.badge {
|
|
627
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
628
|
+
border: 1px solid var(--border); border-radius: var(--radius-pill); padding: 1px 9px;
|
|
629
|
+
font-size: 11px; font-weight: 500; white-space: nowrap;
|
|
630
|
+
}
|
|
631
|
+
.badge.posted, .badge.origin-posted { background: var(--success-tint); border-color: var(--success-border); color: var(--success); }
|
|
632
|
+
.badge.origin-local { background: var(--gray-100); border-color: var(--border); color: var(--muted); }
|
|
633
|
+
.badge.origin-github { background: var(--accent-tint); border-color: var(--accent-border); color: var(--accent); }
|
|
634
|
+
.comment-author { font-weight: 600; color: var(--text); }
|
|
635
|
+
.reply-count { color: var(--muted); font-style: italic; }
|
|
636
|
+
|
|
637
|
+
/* Disclosure (<details>) with an SVG chevron instead of the native marker. */
|
|
638
|
+
.disclosure > summary {
|
|
639
|
+
list-style: none; cursor: pointer;
|
|
640
|
+
display: flex; align-items: flex-start; gap: 6px;
|
|
641
|
+
}
|
|
642
|
+
.disclosure > summary::-webkit-details-marker { display: none; }
|
|
643
|
+
.disclosure-chevron {
|
|
644
|
+
flex: none; margin-top: 2px; color: var(--muted);
|
|
645
|
+
transition: transform 0.12s ease;
|
|
646
|
+
}
|
|
647
|
+
.disclosure[open] > summary > .disclosure-chevron { transform: rotate(90deg); }
|
|
648
|
+
|
|
649
|
+
/* Right icon rail (always visible, pinned to the right edge of the review). */
|
|
650
|
+
.right-rail {
|
|
651
|
+
flex: none; width: 48px; display: flex; flex-direction: column; gap: var(--sp-1);
|
|
652
|
+
align-items: center; padding: var(--sp-2) 0; border-left: 1px solid var(--border);
|
|
653
|
+
background: var(--surface);
|
|
654
|
+
}
|
|
655
|
+
/* Icon-only rail buttons (fixed 34x34 slot). Labels are shown as a near-instant
|
|
656
|
+
tooltip (see .tooltip below), not an expanding pill. */
|
|
657
|
+
.rail-item { position: relative; }
|
|
658
|
+
.rail-btn {
|
|
659
|
+
position: relative; width: 34px; height: 34px; display: inline-flex;
|
|
660
|
+
align-items: center; justify-content: center; box-sizing: border-box;
|
|
661
|
+
border: 1px solid transparent; border-radius: var(--radius-control);
|
|
662
|
+
background: none; color: var(--text-secondary); cursor: pointer;
|
|
663
|
+
padding: 0; transition: background 0.12s ease, color 0.12s ease;
|
|
664
|
+
}
|
|
665
|
+
.rail-btn:hover, .rail-btn:focus-visible { background: var(--gray-100); color: var(--text); }
|
|
666
|
+
.rail-btn.active { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
667
|
+
.rail-btn.active:hover, .rail-btn.active:focus-visible {
|
|
668
|
+
background: var(--accent); border-color: var(--accent); color: #fff;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/* Shared near-instant tooltip (portaled to <body>, so no ancestor clip-path or
|
|
672
|
+
overflow can clip it). Dark pill, white text; appears on hover/focus with no
|
|
673
|
+
show-delay and only a tiny fade; hidden immediately on mouse-out/blur (the
|
|
674
|
+
element is unmounted, so hide is instant). Positioned via inline left/top from
|
|
675
|
+
JS; `transform` places the bubble relative to that anchor point per side. */
|
|
676
|
+
.tooltip-wrap { display: inline-flex; }
|
|
677
|
+
.tooltip {
|
|
678
|
+
position: fixed; z-index: 100; pointer-events: none;
|
|
679
|
+
background: var(--gray-900); color: #fff; border-radius: var(--radius-control);
|
|
680
|
+
padding: 4px 8px; font-size: var(--fs-caption); font-weight: 500; line-height: 1.3;
|
|
681
|
+
white-space: nowrap; box-shadow: var(--shadow-raised);
|
|
682
|
+
animation: tooltip-fade-in 0.04s ease-out;
|
|
683
|
+
}
|
|
684
|
+
/* Placed to the LEFT of the trigger: bubble's right edge sits at the anchor x,
|
|
685
|
+
vertically centered on the anchor y. */
|
|
686
|
+
.tooltip-left { transform: translate(-100%, -50%); }
|
|
687
|
+
/* Placed BELOW the trigger: horizontally centered on the anchor x, top at y. */
|
|
688
|
+
.tooltip-bottom { transform: translateX(-50%); }
|
|
689
|
+
@keyframes tooltip-fade-in { from { opacity: 0; } to { opacity: 1; } }
|
|
690
|
+
/* Miniature notification-style count bubble, anchored to the icon square so it
|
|
691
|
+
stays pinned to the icon's top-right in both rest and expanded states. */
|
|
692
|
+
.rail-badge {
|
|
693
|
+
position: absolute; top: -3px; right: -3px; min-width: 16px; height: 16px;
|
|
694
|
+
padding: 0 4px; box-sizing: border-box; border-radius: var(--radius-pill);
|
|
695
|
+
background: var(--accent); color: #fff; border: 1.5px solid var(--surface);
|
|
696
|
+
font-size: 10px; font-weight: 600; line-height: 13px; text-align: center; z-index: 1;
|
|
697
|
+
}
|
|
698
|
+
.rail-btn.active .rail-badge { background: #fff; color: var(--accent); border-color: var(--accent); }
|
|
699
|
+
|
|
700
|
+
/* The rail's expandable sidebar (pushes the diff aside, no overlay). */
|
|
701
|
+
.rail-sidebar {
|
|
702
|
+
width: 440px; flex: none; border-left: 1px solid var(--border);
|
|
703
|
+
display: flex; flex-direction: column; overflow: hidden;
|
|
704
|
+
background: var(--surface-sunken); position: relative;
|
|
705
|
+
}
|
|
706
|
+
.rail-sidebar[hidden] { display: none; }
|
|
707
|
+
.rail-sidebar-header {
|
|
708
|
+
display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-3);
|
|
709
|
+
border-bottom: 1px solid var(--border); background: var(--surface);
|
|
710
|
+
}
|
|
711
|
+
.rail-sidebar-header strong { flex: 1; font-size: var(--fs-subhead); }
|
|
712
|
+
.rail-sidebar-body { flex: 1; min-height: 0; display: flex; }
|
|
713
|
+
/* Each surface fills the body; inactive ones are display:none (hidden attr) but
|
|
714
|
+
stay mounted so their scroll position and internal state survive tab switches
|
|
715
|
+
and collapse→reopen. */
|
|
716
|
+
.rail-panel { flex: 1; min-width: 0; display: flex; flex-direction: column; overflow: auto; }
|
|
717
|
+
.rail-panel[hidden] { display: none; }
|
|
718
|
+
|
|
719
|
+
/* Comments surface, hosted inside a rail panel. */
|
|
720
|
+
.comments-panel-content { display: flex; flex-direction: column; flex: 1; min-height: 0; }
|
|
721
|
+
.comments-panel-count { padding: var(--sp-2) var(--sp-3) 0; }
|
|
722
|
+
.comments-panel-filters {
|
|
723
|
+
display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2);
|
|
724
|
+
padding: var(--sp-3); border-bottom: 1px solid var(--border); background: var(--surface); font-size: var(--fs-caption);
|
|
725
|
+
}
|
|
726
|
+
.comments-panel-filters label { display: inline-flex; align-items: center; gap: 5px; color: var(--muted); }
|
|
727
|
+
.comments-panel-filters select { max-width: 150px; }
|
|
728
|
+
|
|
729
|
+
/* AI-reviews list, hosted inside a rail panel. */
|
|
730
|
+
.ai-reviews-list { padding: var(--sp-3); }
|
|
731
|
+
|
|
732
|
+
/* Segmented control (a row of mutually-exclusive choices). */
|
|
733
|
+
.segmented {
|
|
734
|
+
display: inline-flex; border: 1px solid var(--border-strong); border-radius: var(--radius-control);
|
|
735
|
+
overflow: hidden; background: var(--surface);
|
|
736
|
+
}
|
|
737
|
+
.segmented-item {
|
|
738
|
+
font: inherit; font-size: var(--fs-caption); color: var(--text-secondary);
|
|
739
|
+
border: none; background: none; padding: 4px 10px; cursor: pointer;
|
|
740
|
+
border-left: 1px solid var(--border); transition: background 0.12s ease, color 0.12s ease;
|
|
741
|
+
}
|
|
742
|
+
.segmented-item:first-child { border-left: none; }
|
|
743
|
+
.segmented-item:hover:not(.active) { background: var(--gray-100); }
|
|
744
|
+
.segmented-item.active { background: var(--accent); color: #fff; }
|
|
745
|
+
/* When pills are wrapped in the hover Tooltip (`.tooltip-wrap`), the wrapper —
|
|
746
|
+
not the button — is the flex child, so carry the divider seam on it. */
|
|
747
|
+
.segmented > .tooltip-wrap { border-left: 1px solid var(--border); }
|
|
748
|
+
.segmented > .tooltip-wrap:first-child { border-left: none; }
|
|
749
|
+
.comments-panel-content .comment-list { overflow: auto; flex: 1; padding: var(--sp-2) var(--sp-3) var(--sp-3); }
|
|
750
|
+
.comments-panel-content .comment-list-item { background: var(--surface); }
|
|
751
|
+
.comments-panel-content .comment-list-meta code {
|
|
752
|
+
max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
753
|
+
background: var(--gray-100); padding: 1px 5px; border-radius: 4px;
|
|
754
|
+
}
|
|
755
|
+
.comment-jump {
|
|
756
|
+
display: block; width: 100%; text-align: left; border: none; background: none;
|
|
757
|
+
padding: 0; margin: 0; cursor: pointer; font: inherit;
|
|
758
|
+
}
|
|
759
|
+
.comment-jump:hover .comment-list-meta code { text-decoration: underline; }
|
|
760
|
+
|
|
761
|
+
/* Transient highlight when scrolling to a comment from the panel */
|
|
762
|
+
@keyframes comment-flash-fade {
|
|
763
|
+
from { background: #fff3bf; box-shadow: 0 0 0 3px #ffe066; }
|
|
764
|
+
to { background: transparent; box-shadow: 0 0 0 3px transparent; }
|
|
765
|
+
}
|
|
766
|
+
.comment-flash { animation: comment-flash-fade 1.8s ease-out; border-radius: var(--radius-card); }
|
|
767
|
+
|
|
768
|
+
/* Out-of-range confirmation popover inside the comments panel */
|
|
769
|
+
.oor-prompt {
|
|
770
|
+
position: absolute; left: var(--sp-3); right: var(--sp-3); bottom: var(--sp-3); z-index: 5;
|
|
771
|
+
background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-card);
|
|
772
|
+
box-shadow: var(--shadow-raised); padding: var(--sp-3) var(--sp-4);
|
|
773
|
+
}
|
|
774
|
+
.oor-text { margin: 0 0 var(--sp-3); font-size: var(--fs-caption); line-height: 1.5; }
|
|
775
|
+
.oor-text code { background: var(--gray-100); padding: 1px 4px; border-radius: 4px; }
|
|
776
|
+
.oor-actions { display: flex; align-items: center; gap: var(--sp-3); }
|
|
777
|
+
.oor-open { display: inline-flex; align-items: center; gap: 4px; font-size: var(--fs-caption); color: var(--accent); text-decoration: none; }
|
|
778
|
+
.oor-open:hover { text-decoration: underline; }
|
|
779
|
+
|
|
780
|
+
/* GitHub threads */
|
|
781
|
+
.post-menu { display: inline-flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
|
|
782
|
+
.post-menu-hint { font-size: 11px; color: var(--muted); }
|
|
783
|
+
.post-busy { font-size: 11px; color: var(--muted); font-style: italic; }
|
|
784
|
+
.post-warning { color: var(--warning); font-size: 11px; }
|
|
785
|
+
|
|
786
|
+
.gh-thread {
|
|
787
|
+
border: 1px solid var(--border); border-left: 3px solid var(--accent); border-radius: var(--radius-card);
|
|
788
|
+
background: var(--surface); padding: var(--sp-2) var(--sp-3); margin: var(--sp-1) 0; box-shadow: var(--shadow-sm);
|
|
789
|
+
}
|
|
790
|
+
.gh-thread-tag {
|
|
791
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
792
|
+
font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--accent); margin-bottom: var(--sp-1); font-weight: 600;
|
|
793
|
+
}
|
|
794
|
+
.gh-comment { border-top: 1px solid var(--border); padding-top: var(--sp-2); margin-top: var(--sp-2); }
|
|
795
|
+
.gh-comment:first-of-type { border-top: none; padding-top: 0; margin-top: 0; }
|
|
796
|
+
.gh-comment .comment-meta strong { color: var(--text); }
|
|
797
|
+
|
|
798
|
+
/* Symbol lookup menu + panel */
|
|
799
|
+
.symbol-menu {
|
|
800
|
+
position: fixed; z-index: 120; display: inline-flex; align-items: center; gap: var(--sp-2);
|
|
801
|
+
background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-card);
|
|
802
|
+
box-shadow: var(--shadow-raised); padding: 5px 8px; font-size: var(--fs-caption);
|
|
803
|
+
}
|
|
804
|
+
.symbol-menu-term { background: var(--gray-100); padding: 2px 6px; border-radius: 4px; max-width: 160px; overflow: hidden; text-overflow: ellipsis; }
|
|
805
|
+
.symbol-menu-side { display: inline-flex; border: 1px solid var(--border-strong); border-radius: var(--radius-control); overflow: hidden; }
|
|
806
|
+
.symbol-menu-side button { border: none; background: var(--surface); font: inherit; font-size: var(--fs-caption); padding: 3px 9px; cursor: pointer; color: var(--text-secondary); }
|
|
807
|
+
.symbol-menu-side button:hover { background: var(--gray-100); }
|
|
808
|
+
.symbol-menu-side button.active { background: var(--accent); color: #fff; }
|
|
809
|
+
|
|
810
|
+
.symbol-side-tag { font-size: 10px; text-transform: uppercase; color: var(--muted); border: 1px solid var(--border); border-radius: var(--radius-pill); padding: 1px 8px; }
|
|
811
|
+
.symbol-panel-close {
|
|
812
|
+
border: none; background: none; cursor: pointer; color: var(--muted);
|
|
813
|
+
display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: var(--radius-control); flex: none;
|
|
814
|
+
}
|
|
815
|
+
.symbol-panel-close:hover { background: var(--gray-100); color: var(--text); }
|
|
816
|
+
|
|
817
|
+
/* Search rail tab: controls + shared results list. */
|
|
818
|
+
.search-rail-panel { display: flex; flex-direction: column; flex: 1; min-height: 0; }
|
|
819
|
+
.search-rail-controls {
|
|
820
|
+
display: flex; flex-direction: column; gap: var(--sp-2); padding: var(--sp-3);
|
|
821
|
+
border-bottom: 1px solid var(--border); background: var(--surface);
|
|
822
|
+
}
|
|
823
|
+
.search-rail-query { width: 100%; }
|
|
824
|
+
.search-rail-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3); font-size: var(--fs-caption); }
|
|
825
|
+
.search-rail-run { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
|
|
826
|
+
.search-rail-stale { font-size: var(--fs-caption); color: var(--accent); }
|
|
827
|
+
|
|
828
|
+
.results-list { display: flex; flex-direction: column; flex: 1; min-height: 0; }
|
|
829
|
+
.results-list:focus { outline: none; }
|
|
830
|
+
.results-header {
|
|
831
|
+
display: flex; align-items: center; gap: var(--sp-2); padding: var(--sp-2) var(--sp-3);
|
|
832
|
+
font-size: var(--fs-caption); border-bottom: 1px solid var(--border); background: var(--surface);
|
|
833
|
+
}
|
|
834
|
+
.results-header strong { font-size: var(--fs-subhead); }
|
|
835
|
+
.results-header code { overflow: hidden; text-overflow: ellipsis; background: var(--gray-100); padding: 1px 5px; border-radius: 4px; }
|
|
836
|
+
.results-count { margin-left: auto; color: var(--muted); }
|
|
837
|
+
.results-scope { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); }
|
|
838
|
+
.results-scope code { background: var(--gray-100); padding: 1px 5px; border-radius: 4px; }
|
|
839
|
+
/* Always-on usages caveat under the results header. */
|
|
840
|
+
.results-note {
|
|
841
|
+
display: flex; align-items: center; gap: 6px; margin: 0;
|
|
842
|
+
padding: var(--sp-1) var(--sp-3); font-size: var(--fs-caption); color: var(--muted);
|
|
843
|
+
background: var(--surface-sunken); border-bottom: 1px solid var(--border);
|
|
844
|
+
}
|
|
845
|
+
.results-note-info {
|
|
846
|
+
display: inline-flex; align-items: center; padding: 0; border: 0; background: none;
|
|
847
|
+
color: var(--muted); cursor: help;
|
|
848
|
+
}
|
|
849
|
+
.results-note-info:hover { color: var(--accent); }
|
|
850
|
+
.results-filters {
|
|
851
|
+
display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2);
|
|
852
|
+
padding: var(--sp-2) var(--sp-3); border-bottom: 1px solid var(--border); font-size: var(--fs-caption);
|
|
853
|
+
}
|
|
854
|
+
.results-filters input[type="text"] { flex: 1; min-width: 90px; }
|
|
855
|
+
.results-toggle { display: inline-flex; align-items: center; gap: 5px; color: var(--muted); }
|
|
856
|
+
.results-error { color: var(--danger); }
|
|
857
|
+
.results-items { list-style: none; margin: 0; padding: 0; overflow: auto; flex: 1; }
|
|
858
|
+
.results-item { border-bottom: 1px solid var(--border); padding: var(--sp-2) var(--sp-3); }
|
|
859
|
+
.results-item.focused { background: var(--accent-tint); }
|
|
860
|
+
.results-item-loc { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-caption); margin-bottom: var(--sp-1); }
|
|
861
|
+
.results-item-loc code { overflow: hidden; text-overflow: ellipsis; }
|
|
862
|
+
.results-item-scope {
|
|
863
|
+
font-size: 10px; color: var(--muted); border: 1px solid var(--border);
|
|
864
|
+
border-radius: var(--radius-pill); padding: 1px 7px; white-space: nowrap; margin-left: auto;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/* Compact code preview (definition body or before/matched/after context). */
|
|
868
|
+
.code-preview {
|
|
869
|
+
border-collapse: collapse; width: 100%; font-family: var(--mono); font-size: 11px;
|
|
870
|
+
line-height: 1.5; table-layout: fixed; background: var(--surface);
|
|
871
|
+
border: 1px solid var(--border); border-radius: var(--radius-control); overflow: hidden;
|
|
872
|
+
}
|
|
873
|
+
.code-preview td { padding: 0 8px; vertical-align: top; white-space: pre-wrap; word-break: break-all; }
|
|
874
|
+
.code-preview-no { width: 44px; text-align: right; color: var(--muted); user-select: none; border-right: 1px solid var(--border); }
|
|
875
|
+
.code-preview-line.matched { background: var(--accent-tint); }
|
|
876
|
+
.code-preview-line.matched .code-preview-no { color: var(--accent); font-weight: 600; }
|
|
877
|
+
|
|
878
|
+
/* AI chat panel */
|
|
879
|
+
.chat-panel { width: 420px; border-left: 1px solid var(--border); display: flex; flex-direction: column; background: var(--surface-sunken); }
|
|
880
|
+
.chat-panel-header { display: flex; align-items: center; gap: var(--sp-2); padding: var(--sp-3); border-bottom: 1px solid var(--border); background: var(--surface); }
|
|
881
|
+
.chat-panel-header strong { display: inline-flex; align-items: center; gap: 5px; }
|
|
882
|
+
.chat-scope { flex: 1; font-size: var(--fs-caption); color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
883
|
+
.chat-controls { display: flex; gap: var(--sp-2); padding: var(--sp-3); border-bottom: 1px solid var(--border); }
|
|
884
|
+
.chat-controls select { flex: 1; }
|
|
885
|
+
.chat-transcript { flex: 1; overflow: auto; padding: var(--sp-3); display: flex; flex-direction: column; gap: var(--sp-3); scroll-behavior: smooth; }
|
|
886
|
+
.chat-msg { border: 1px solid var(--border); border-radius: var(--radius-card); padding: var(--sp-2) var(--sp-3); background: var(--surface); box-shadow: var(--shadow-sm); }
|
|
887
|
+
.chat-user { background: var(--accent-tint); border-color: var(--accent-border); align-self: flex-end; max-width: 92%; }
|
|
888
|
+
.chat-assistant { align-self: flex-start; max-width: 98%; }
|
|
889
|
+
.chat-msg-head { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-1); }
|
|
890
|
+
.chat-role { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); }
|
|
891
|
+
.chat-time { font-size: 10px; color: var(--muted); }
|
|
892
|
+
.chat-copy { margin-left: auto; opacity: 0; transition: opacity 0.12s; }
|
|
893
|
+
.chat-msg:hover .chat-copy { opacity: 1; }
|
|
894
|
+
.chat-error { color: var(--danger); }
|
|
895
|
+
.chat-msg .comment-body pre { overflow-x: auto; }
|
|
896
|
+
|
|
897
|
+
/* "Agent is working" indicator */
|
|
898
|
+
.chat-working { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-caption); color: var(--muted); margin-top: var(--sp-2); }
|
|
899
|
+
.chat-working-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
900
|
+
.chat-elapsed { font-variant-numeric: tabular-nums; font-size: 11px; opacity: 0.8; }
|
|
901
|
+
.chat-spinner { width: 12px; height: 12px; border: 2px solid var(--gray-300); border-top-color: var(--accent); border-radius: 50%; animation: chat-spin 0.7s linear infinite; flex: none; }
|
|
902
|
+
@keyframes chat-spin { to { transform: rotate(360deg); } }
|
|
903
|
+
.chat-input { display: flex; flex-direction: column; gap: var(--sp-2); padding: var(--sp-3); border-top: 1px solid var(--border); background: var(--surface); }
|
|
904
|
+
.chat-input button { align-self: flex-end; }
|
|
905
|
+
.chat-artifacts { border-top: 1px solid var(--border); padding: var(--sp-2) var(--sp-3); background: var(--surface); font-size: var(--fs-caption); }
|
|
906
|
+
.chat-artifacts ul { list-style: none; margin: var(--sp-1) 0 0; padding: 0; }
|
|
907
|
+
.chat-artifacts a { color: var(--accent); text-decoration: none; }
|
|
908
|
+
|
|
909
|
+
/* AI review modal */
|
|
910
|
+
.ai-review-modal { width: 820px; max-width: 92vw; max-height: 88vh; }
|
|
911
|
+
.ai-review-body { overflow: auto; padding: var(--sp-4) var(--sp-6); }
|
|
912
|
+
.ai-review-form { display: flex; flex-direction: column; gap: var(--sp-3); margin-bottom: var(--sp-4); }
|
|
913
|
+
.ai-review-form label { display: flex; flex-direction: column; gap: 4px; font-size: var(--fs-caption); color: var(--muted); }
|
|
914
|
+
.ai-review-form select { align-self: flex-start; min-width: 240px; }
|
|
915
|
+
.ai-review-form button { align-self: flex-start; }
|
|
916
|
+
.ai-review-past { border-top: 1px solid var(--border); padding-top: var(--sp-3); }
|
|
917
|
+
.ai-review-item { margin: var(--sp-2) 0; }
|
|
918
|
+
.ai-review-item summary { cursor: pointer; font-size: var(--fs-caption); color: var(--muted); }
|
|
919
|
+
.ai-review-running { display: flex; align-items: center; gap: var(--sp-2); font-style: normal; }
|
|
920
|
+
|
|
921
|
+
/* App-level AI-review progress indicator in the header. */
|
|
922
|
+
.ai-indicator {
|
|
923
|
+
display: inline-flex; align-items: center; gap: 5px; font: inherit; font-size: var(--fs-caption);
|
|
924
|
+
font-weight: 500; padding: 5px 10px; border-radius: var(--radius-pill); white-space: nowrap;
|
|
925
|
+
border: 1px solid transparent;
|
|
926
|
+
}
|
|
927
|
+
button.ai-indicator { cursor: pointer; }
|
|
928
|
+
.ai-indicator-running { background: var(--accent-tint); border-color: var(--accent-border); color: var(--accent); }
|
|
929
|
+
.ai-indicator-done { background: var(--success-tint); border-color: var(--success-border); color: var(--success); }
|
|
930
|
+
.ai-indicator-done:hover { background: var(--success-tint-hover); }
|
|
931
|
+
.ai-indicator-failed { background: var(--danger-tint); border-color: var(--danger-border); color: var(--danger); }
|
|
932
|
+
.ai-indicator-failed:hover { background: var(--danger-tint-hover); }
|
|
933
|
+
.ai-indicator .chat-spinner { width: 12px; height: 12px; }
|
|
934
|
+
|
|
935
|
+
/* Inline confirm (destructive actions) */
|
|
936
|
+
.confirm-inline { display: inline-flex; align-items: center; gap: var(--sp-2); }
|
|
937
|
+
.confirm-warn { color: var(--warning); font-size: 11px; }
|
|
938
|
+
|
|
939
|
+
/* Stale (force-push) indication */
|
|
940
|
+
.badge.stale { background: var(--warning-tint); border-color: var(--warning-border); color: var(--warning); }
|
|
941
|
+
.reviewed-list { list-style: none; margin: var(--sp-3) 0 0; padding: var(--sp-2) 0 0; border-top: 1px solid var(--border); display: flex; flex-direction: column; gap: var(--sp-1); }
|
|
942
|
+
.reviewed-list li { display: flex; align-items: center; gap: var(--sp-2); }
|
|
943
|
+
.reviewed-range { border: 1px solid var(--border-strong); background: var(--surface); border-radius: var(--radius-control); padding: 3px 9px; cursor: pointer; font-size: var(--fs-caption); }
|
|
944
|
+
.reviewed-range:hover:not(:disabled) { background: var(--gray-100); }
|
|
945
|
+
.reviewed-range:disabled { opacity: 0.5; cursor: not-allowed; text-decoration: line-through; }
|
|
946
|
+
|
|
947
|
+
/* ── Landing / PR list ─────────────────────────────────────────────── */
|
|
948
|
+
.landing { max-width: 760px; margin: 0 auto; padding: 56px var(--sp-6) var(--sp-8); }
|
|
949
|
+
.landing-brand { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-1); }
|
|
950
|
+
.landing-wordmark { font-size: 26px; font-weight: 700; letter-spacing: -0.01em; margin: 0; }
|
|
951
|
+
.landing-mark {
|
|
952
|
+
display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px;
|
|
953
|
+
border-radius: 9px; background: var(--accent); color: #fff;
|
|
954
|
+
}
|
|
955
|
+
.landing-tagline { color: var(--muted); margin: 0 0 var(--sp-6); font-size: var(--fs-subhead); }
|
|
956
|
+
.pr-cards { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
|
|
957
|
+
.pr-card {
|
|
958
|
+
display: flex; flex-direction: column; gap: 4px; padding: var(--sp-4);
|
|
959
|
+
border: 1px solid var(--border); border-radius: var(--radius-card); background: var(--surface);
|
|
960
|
+
box-shadow: var(--shadow-card); text-decoration: none; color: var(--text);
|
|
961
|
+
transition: border-color 0.12s ease, box-shadow 0.12s ease, transform 0.06s ease;
|
|
962
|
+
}
|
|
963
|
+
.pr-card:hover { border-color: var(--accent-border); box-shadow: var(--shadow-raised); }
|
|
964
|
+
.pr-card:active { transform: translateY(1px); }
|
|
965
|
+
.pr-card-meta { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-caption); color: var(--muted); }
|
|
966
|
+
.pr-card-repo { font-family: var(--mono); }
|
|
967
|
+
.pr-card-num { color: var(--accent); font-weight: 600; }
|
|
968
|
+
.pr-card-title { font-size: var(--fs-subhead); font-weight: 600; }
|
|
969
|
+
.pr-card-author { font-size: var(--fs-caption); color: var(--muted); }
|
|
970
|
+
|
|
971
|
+
/* ── PR picker (home + Switch-PR modal) ────────────────────────────── */
|
|
972
|
+
.picker { display: flex; flex-direction: column; gap: var(--sp-4); }
|
|
973
|
+
.picker-url { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; }
|
|
974
|
+
.picker-url-input { flex: 1; min-width: 240px; }
|
|
975
|
+
.picker-url-error { flex-basis: 100%; margin: 0; font-size: var(--fs-caption); color: var(--danger); }
|
|
976
|
+
.picker-filter { position: relative; display: flex; align-items: center; }
|
|
977
|
+
.picker-filter .icon { position: absolute; left: 9px; color: var(--gray-500); pointer-events: none; }
|
|
978
|
+
.picker-filter .picker-filter-input { width: 100%; padding: 6px 10px 6px 30px; }
|
|
979
|
+
.picker-section { display: flex; flex-direction: column; gap: var(--sp-2); }
|
|
980
|
+
.picker-section-head { display: flex; align-items: center; justify-content: space-between; }
|
|
981
|
+
.picker-section-title {
|
|
982
|
+
margin: 0; font-size: var(--fs-caption); font-weight: 600; text-transform: uppercase;
|
|
983
|
+
letter-spacing: 0.04em; color: var(--muted);
|
|
984
|
+
}
|
|
985
|
+
.pr-chip {
|
|
986
|
+
display: inline-flex; align-items: center; padding: 1px 7px; border-radius: 999px;
|
|
987
|
+
font-size: 11px; font-weight: 600; background: var(--gray-100); color: var(--text-secondary);
|
|
988
|
+
border: 1px solid var(--border);
|
|
989
|
+
}
|
|
990
|
+
.pr-chip-draft { background: var(--gray-200); color: var(--muted); }
|
|
991
|
+
.pr-chip-current { background: var(--accent-soft, var(--gray-100)); color: var(--accent); border-color: var(--accent-border); }
|
|
992
|
+
.pr-card-current { opacity: 0.75; cursor: default; box-shadow: none; }
|
|
993
|
+
.pr-card-current:hover { border-color: var(--border); box-shadow: none; }
|
|
994
|
+
.picker-status { display: flex; align-items: center; gap: var(--sp-2); padding: var(--sp-4); color: var(--muted); font-size: var(--fs-secondary); }
|
|
995
|
+
.picker-error { color: var(--danger); }
|
|
996
|
+
|
|
997
|
+
/* Loading gate shown after picking a not-yet-loaded PR. */
|
|
998
|
+
.pr-loading, .pr-loading-fail {
|
|
999
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
1000
|
+
gap: var(--sp-2); text-align: center; padding: 72px var(--sp-6); color: var(--muted);
|
|
1001
|
+
}
|
|
1002
|
+
.pr-loading .chat-spinner { width: 22px; height: 22px; }
|
|
1003
|
+
.pr-loading-fail .btn { margin-top: var(--sp-3); }
|
|
1004
|
+
|
|
1005
|
+
/* The Switch-PR modal reuses the generic modal shell at a comfortable width. */
|
|
1006
|
+
.picker-modal { width: min(680px, 92vw); max-height: 86vh; }
|
|
1007
|
+
.picker-modal-body { padding: var(--sp-4); overflow-y: auto; }
|
|
1008
|
+
|
|
1009
|
+
/* ── Shared empty state ────────────────────────────────────────────── */
|
|
1010
|
+
.empty-state {
|
|
1011
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
1012
|
+
gap: var(--sp-2); text-align: center; padding: 64px var(--sp-6); color: var(--muted);
|
|
1013
|
+
}
|
|
1014
|
+
.empty-state .icon { color: var(--gray-400); }
|
|
1015
|
+
.empty-state-title { font-size: var(--fs-subhead); font-weight: 600; color: var(--text-secondary); font-style: normal; margin: var(--sp-1) 0 0; }
|
|
1016
|
+
.empty-state-hint { font-size: var(--fs-secondary); max-width: 360px; }
|
|
1017
|
+
|
|
1018
|
+
/* Diff loading spinner block. */
|
|
1019
|
+
.diff-loading { display: flex; align-items: center; gap: var(--sp-2); padding: var(--sp-4); color: var(--muted); }
|
|
1020
|
+
.diff-loading .chat-spinner { width: 16px; height: 16px; }
|