@ok-ggwp/git-gloss 0.3.0 → 0.5.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/dist/html/data.js CHANGED
@@ -2,8 +2,10 @@ import { commitDiff, commitSubject, git } from '../git.js';
2
2
  import { reviewStatus, statusPhrase, verdict } from '../reviews.js';
3
3
  import { parseDiff } from '../diff.js';
4
4
  import { loadGloss } from '../loader.js';
5
+ import { belowFloor } from '../render.js';
6
+ import { showsImpact, tryAnalyze } from '../floor.js';
5
7
  import { bodyLines, changedCount, gapText, isGap, refLabel, slice, sliceStats } from '../selection.js';
6
- import { NECESSITY, NECESSITY_HELP, servesOf } from '../schema.js';
8
+ import { IMPACT, IMPACT_HELP, NECESSITY, NECESSITY_HELP, servesOf } from '../schema.js';
7
9
  function pageReviews(sha) {
8
10
  return reviewStatus(sha).map((s) => ({
9
11
  stage: s.stage,
@@ -57,10 +59,23 @@ function pageHunk(h, lines, status, alsoIn) {
57
59
  lines: pageLines(h, lines),
58
60
  };
59
61
  }
62
+ // The order a reviewer should read a commit's groups in. Deterministic: a
63
+ // stable sort over the groups as they come, so ties keep their order.
64
+ export function reviewOrder(groups, focus = []) {
65
+ const rank = (g) => IMPACT.indexOf((g.impact ?? g.impact_derived)) + 1; // 0: unrated
66
+ const first = (g) => (focus.includes(g.id) ? focus.indexOf(g.id) : focus.length);
67
+ return groups
68
+ .map((g, i) => ({ g, i }))
69
+ .sort((a, b) => first(a.g) - first(b.g) || rank(b.g) - rank(a.g) || (b.g.added + b.g.removed) - (a.g.added + a.g.removed) || a.i - b.i)
70
+ .map((x) => x.g.id);
71
+ }
60
72
  const allLines = (h) => Array.from({ length: changedCount(h) }, (_, i) => i + 1);
61
73
  export function commitData(gloss) {
62
74
  const [author, date] = git(['log', '-1', '--format=%an%x00%aI', gloss.sha]).trim().split('\0');
63
75
  const a = gloss.note.annotation;
76
+ const an = tryAnalyze(gloss);
77
+ // Same threshold as `show`: derived impact badges only where the diff puts a group above module.
78
+ const derivedImpact = showsImpact(gloss.groups.map((x) => x.group), an);
64
79
  // Which groups claim each changed line, so shared lines are split between
65
80
  // their groups in the bar and each hunk can link to the groups it shares
66
81
  // lines with. A header-only hunk counts as one shared unit.
@@ -85,6 +100,9 @@ export function commitData(gloss) {
85
100
  id: g.id,
86
101
  intent: g.intent,
87
102
  necessity: g.necessity,
103
+ impact: g.impact,
104
+ impact_reason: g.impact_reason,
105
+ impact_derived: g.impact || !derivedImpact ? undefined : an?.groups.get(g.id)?.floor,
88
106
  serves: servesOf(g),
89
107
  removal_impact: g.removal_impact,
90
108
  evidence: g.evidence,
@@ -108,8 +126,11 @@ export function commitData(gloss) {
108
126
  glossed: true,
109
127
  task: a.task,
110
128
  summary: a.summary,
129
+ attention: a.attention ?? null,
130
+ computed: an ? { floor: an.floor, derived: an.derived, below: belowFloor(a.attention, an), timedOut: an.timedOut } : null,
111
131
  warnings: gloss.warnings,
112
132
  groups,
133
+ order: reviewOrder(groups, a.attention?.focus),
113
134
  unannotated: gloss.unannotated.map((u) => pageHunk(u.hunk, u.lines, 'unannotated', [])),
114
135
  alternatives: a.alternatives ?? [],
115
136
  risks: a.risks ?? [],
@@ -125,8 +146,11 @@ function unglossedData(sha) {
125
146
  date,
126
147
  glossed: false,
127
148
  task: '',
149
+ attention: null,
150
+ computed: null,
128
151
  warnings: [],
129
152
  groups: [],
153
+ order: [],
130
154
  unannotated: parseDiff(commitDiff(sha)).map((h) => pageHunk(h, null, 'unannotated', [])),
131
155
  alternatives: [],
132
156
  risks: [],
@@ -138,6 +162,7 @@ export function buildPageData(shas, range = null, merges = []) {
138
162
  range,
139
163
  merges: merges.map((sha) => ({ sha, subject: commitSubject(sha) })),
140
164
  necessity: NECESSITY.map((name) => ({ name, help: NECESSITY_HELP[name] })),
165
+ impact: IMPACT.map((name) => ({ name, help: IMPACT_HELP[name] })),
141
166
  commits: shas.map((sha) => {
142
167
  const gloss = loadGloss(sha);
143
168
  return gloss ? commitData(gloss) : unglossedData(sha);
package/dist/html/page.js CHANGED
@@ -27,7 +27,7 @@ ${asset('./style.css')}
27
27
  <div id="app"><noscript>This page needs JavaScript to render the gloss.</noscript></div>
28
28
  <script type="application/json" id="gloss-data">${embedJson(data)}</script>
29
29
  <script nonce="${nonce}">
30
- ${asset('./client.js')}
30
+ ${data.ary ? asset('../ary/picker.js') + '\n' : ''}${asset('./client.js')}
31
31
  </script>
32
32
  </body>
33
33
  </html>
@@ -1,19 +1,24 @@
1
+ /* Review desk: a rail of groups (or commits) and a detail pane. System fonts
2
+ only: the page is one file and its CSP allows no other source. */
1
3
  :root {
2
- --bg: #f7f7f5;
3
- --surface: #ffffff;
4
- --surface-2: #f1f1ee;
5
- --border: #e2e2dc;
6
- --ok: #1a7f37;
7
- --text: #1d1d1b;
8
- --muted: #6b6b66;
9
- --accent: #3b5bdb;
4
+ --bg: #f3f4f6;
5
+ --panel: #ffffff;
6
+ --ink: #14171f;
7
+ --muted: #586073;
8
+ --line: #dde0e6;
9
+ --accent: #3446d6;
10
+ --accent-soft: #e7eafb;
11
+ --accent-strong: #2536a8;
12
+ --on-accent: #ffffff;
13
+ --ok: #1b6b35;
14
+ --warn: #a5530a;
10
15
  --warn-bg: #fff4e5;
11
16
  --warn-border: #f0b35b;
12
17
  --warn-text: #7a4300;
13
- --add-bg: #e6f6ea;
14
- --add-mark: #1a7f37;
15
- --del-bg: #fcebec;
16
- --del-mark: #c62a2f;
18
+ --add-bg: #e3f5e8;
19
+ --add-mark: #1b6b35;
20
+ --del-bg: #fbe7e8;
21
+ --del-mark: #b3262b;
17
22
  --meta-bg: #eef1f8;
18
23
  --meta-text: #4a5a85;
19
24
 
@@ -24,6 +29,19 @@
24
29
  --n-speculative: #9c36b5;
25
30
  --n-mechanical: #868e96;
26
31
 
32
+ /* Impact meter and attention gauge: one hue per step, and the steps differ
33
+ in lightness too, so they read without colour. */
34
+ --impact-local: #5b6373;
35
+ --impact-module: #0b6e83;
36
+ --impact-shared: #a5530a;
37
+ --impact-contract: #6b46c1;
38
+ --impact-sensitive: #b3262b;
39
+ --attention-skim: #5b6373;
40
+ --attention-look: #0b6e83;
41
+ --attention-careful: #a5530a;
42
+ --attention-must-read: #b3262b;
43
+ --attention-none: #586073;
44
+
27
45
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
28
46
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
29
47
  color-scheme: light;
@@ -31,21 +49,24 @@
31
49
 
32
50
  @media (prefers-color-scheme: dark) {
33
51
  :root {
34
- --bg: #151514;
35
- --surface: #1e1e1c;
36
- --surface-2: #262624;
37
- --border: #34342f;
38
- --ok: #57c47a;
39
- --text: #e8e8e3;
40
- --muted: #9a9a92;
41
- --accent: #8ea2ff;
52
+ --bg: #101218;
53
+ --panel: #181b23;
54
+ --ink: #e8eaf0;
55
+ --muted: #a0a7b6;
56
+ --line: #2b303c;
57
+ --accent: #8fa0ff;
58
+ --accent-soft: #232a4a;
59
+ --accent-strong: #b3bfff;
60
+ --on-accent: #101218;
61
+ --ok: #62d089;
62
+ --warn: #f0a24a;
42
63
  --warn-bg: #33270f;
43
64
  --warn-border: #a2701e;
44
65
  --warn-text: #f3c77a;
45
- --add-bg: #15301e;
46
- --add-mark: #5ccf7f;
47
- --del-bg: #3a1a1c;
48
- --del-mark: #ff7b80;
66
+ --add-bg: #14301d;
67
+ --add-mark: #62d089;
68
+ --del-bg: #3a1b1f;
69
+ --del-mark: #ff8085;
49
70
  --meta-bg: #20263a;
50
71
  --meta-text: #a3b1dc;
51
72
 
@@ -55,143 +76,209 @@
55
76
  --n-incidental: #e0a43e;
56
77
  --n-speculative: #cf7ee4;
57
78
  --n-mechanical: #8d949b;
79
+
80
+ --impact-local: #a3aab8;
81
+ --impact-module: #4cc3d9;
82
+ --impact-shared: #f0a24a;
83
+ --impact-contract: #b39dff;
84
+ --impact-sensitive: #ff8085;
85
+ --attention-skim: #a3aab8;
86
+ --attention-look: #4cc3d9;
87
+ --attention-careful: #f0a24a;
88
+ --attention-must-read: #ff8085;
89
+ --attention-none: #a0a7b6;
58
90
  color-scheme: dark;
59
91
  }
60
92
  }
61
93
 
62
94
  * { box-sizing: border-box; }
63
95
  html { -webkit-text-size-adjust: 100%; }
64
- body {
65
- margin: 0;
66
- background: var(--bg);
67
- color: var(--text);
68
- font: 15px/1.55 var(--sans);
69
- }
70
- main { min-width: 0; }
96
+ body { margin: 0; background: var(--bg); color: var(--ink); font: 14px/1.5 var(--sans); }
71
97
  a { color: var(--accent); text-decoration: none; }
72
98
  a:hover { text-decoration: underline; }
73
- code, .mono { font-family: var(--mono); font-size: 0.86em; }
74
-
75
- .layout {
76
- display: grid;
77
- /* minmax(0, …) so a wide diff scrolls inside its box instead of widening the page */
78
- grid-template-columns: minmax(0, 1fr);
79
- max-width: 1280px;
80
- margin: 0 auto;
81
- padding: 24px 16px 64px;
82
- gap: 32px;
83
- }
84
- @media (min-width: 1100px) {
85
- .layout { grid-template-columns: 240px minmax(0, 1fr); padding: 32px 32px 80px; }
86
- }
99
+ button { font: inherit; color: inherit; cursor: pointer; }
100
+ button:disabled { cursor: default; opacity: 0.5; }
101
+ :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
102
+ code, .mono { font-family: var(--mono); font-size: 12px; }
103
+ .eyebrow { font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
104
+ .muted { color: var(--muted); }
105
+ kbd { font-family: var(--mono); font-size: 11px; border: 1px solid var(--line); border-bottom-width: 2px; border-radius: 4px; padding: 0 5px; background: var(--panel); }
87
106
 
88
- /* Sidebar */
89
- .toc { display: none; }
90
- @media (min-width: 1100px) {
91
- .toc { display: block; position: sticky; top: 24px; align-self: start; max-height: calc(100vh - 48px); overflow: auto; }
92
- }
93
- .toc h2 { font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); margin: 0 0 8px; }
94
- .toc ol { list-style: none; margin: 0 0 24px; padding: 0; }
95
- .toc li a {
96
- display: flex; align-items: center; gap: 8px;
97
- padding: 4px 8px; border-radius: 6px; color: var(--text);
98
- font-family: var(--mono); font-size: 12.5px;
99
- }
100
- .toc li a:hover { background: var(--surface-2); text-decoration: none; }
101
- .toc li.hidden a { opacity: 0.35; }
102
- .toc li.active a { background: var(--surface-2); }
103
-
104
- .dot { width: 8px; height: 8px; border-radius: 50%; flex: none; background: var(--c); }
105
-
106
- /* Header */
107
- .commit { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 12px; color: var(--muted); font-size: 13px; }
108
- .sha { font-family: var(--mono); background: var(--surface-2); border: 1px solid var(--border); padding: 1px 6px; border-radius: 5px; color: var(--text); }
109
- h1 { font-size: 24px; line-height: 1.3; margin: 8px 0 16px; letter-spacing: -0.01em; }
110
- .statement { margin: 0 0 8px; max-width: 75ch; }
111
- .statement .label { color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em; margin-right: 6px; }
112
-
113
- .warning {
114
- margin: 16px 0; padding: 12px 14px; border-radius: 8px;
115
- background: var(--warn-bg); border: 1px solid var(--warn-border); color: var(--warn-text);
116
- }
107
+ .desk { min-height: 100vh; display: flex; flex-direction: column; }
117
108
 
118
- /* Necessity overview */
119
- .overview { margin: 24px 0 8px; }
120
- .bar { display: flex; height: 10px; border-radius: 5px; overflow: hidden; background: var(--surface-2); }
121
- .bar span { display: block; height: 100%; background: var(--c); min-width: 2px; }
122
- .bar span + span { border-left: 2px solid var(--bg); }
123
- .chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
124
- .chip {
125
- display: inline-flex; align-items: center; gap: 6px;
126
- border: 1px solid var(--border); background: var(--surface); color: var(--text);
127
- border-radius: 999px; padding: 4px 10px; font: inherit; font-size: 13px; cursor: pointer;
109
+ /* Top bar */
110
+ .top { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 20px; padding: 10px 16px; background: var(--panel); border-bottom: 1px solid var(--line); }
111
+ .brand { font-weight: 600; letter-spacing: -0.01em; }
112
+ .sha { font-family: var(--mono); font-size: 12px; background: var(--bg); border: 1px solid var(--line); border-radius: 5px; padding: 1px 6px; color: var(--ink); }
113
+ .subject { flex: 1 1 260px; min-width: 0; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
114
+ .seg { display: flex; flex-wrap: wrap; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; background: var(--bg); }
115
+ .seg button { border: 0; background: transparent; padding: 6px 12px; min-height: 44px; color: var(--muted); }
116
+ .seg button[aria-pressed="true"] { background: var(--ink); color: var(--panel); font-weight: 500; }
117
+ .seg .n { opacity: 0.75; margin-left: 4px; font-variant-numeric: tabular-nums; }
118
+ .prog { display: flex; align-items: center; gap: 10px; color: var(--muted); font-size: 13px; }
119
+ .track { width: 120px; height: 6px; border-radius: 3px; background: var(--line); overflow: hidden; }
120
+ .track i { display: block; height: 100%; width: 0; background: var(--accent); }
121
+
122
+ /* Branch pickers and revision box in the top bar, only when served by `git gloss ary` */
123
+ .ary-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; min-width: 0; }
124
+ .ary-repo { color: var(--muted); font-family: var(--mono); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 200px; }
125
+ .ary-field { display: flex; align-items: center; gap: 6px; color: var(--muted); font-size: 12px; }
126
+ .ary-notice { flex-basis: 100%; margin: 0; padding: 6px 10px; border-radius: 8px; border: 1px dashed var(--warn); font-size: 13px; color: var(--ink); }
127
+ .combo { position: relative; }
128
+ .combo-btn, .combo-panel input, .ary-rev input { border: 1px solid var(--line); background: var(--panel); color: var(--ink); border-radius: 8px; min-height: 36px; padding: 4px 12px; font-size: 13px; }
129
+ .combo-btn { position: relative; max-width: 220px; padding-right: 28px; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: var(--mono); font-size: 12.5px; }
130
+ .combo-btn::after { content: "▾"; position: absolute; right: 10px; color: var(--muted); }
131
+ .combo-btn:hover { background: var(--accent-soft); }
132
+ .combo-panel { position: absolute; z-index: 10; left: 0; top: calc(100% + 4px); width: 300px; max-width: calc(100vw - 32px); display: grid; gap: 6px; padding: 6px; border: 1px solid var(--line); border-radius: 10px; background: var(--panel); box-shadow: 0 8px 24px rgb(0 0 0 / 0.25); }
133
+ .combo-panel[hidden] { display: none; }
134
+ .combo-panel input { width: 100%; }
135
+ .combo-list { max-height: min(50vh, 320px); overflow: auto; }
136
+ .combo-group { margin-top: 8px; padding: 10px 8px 4px; border-top: 1px solid var(--line); font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink); }
137
+ .combo-group:first-child { margin-top: 0; padding-top: 4px; border-top: 0; }
138
+ .combo-group .count { margin-left: 6px; color: var(--muted); font-weight: 400; }
139
+ .combo-item { display: block; width: 100%; text-align: left; border: 0; background: none; border-radius: 6px; padding: 5px 8px 5px 20px; font-family: var(--mono); font-size: 12.5px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
140
+ .combo-item:hover, .combo-item.active { background: var(--accent-soft); }
141
+ .combo-item[aria-selected="true"] { color: var(--accent); font-weight: 600; }
142
+ .combo-more { padding: 6px 8px; color: var(--muted); font-size: 12.5px; }
143
+ .ary-rev input { width: 220px; max-width: 100%; font-family: var(--mono); font-size: 12.5px; }
144
+
145
+ /* Body: rail above the pane when narrow, beside it from 900px */
146
+ .body { flex: 1; display: grid; grid-template-columns: minmax(0, 1fr); align-items: start; }
147
+ @media (min-width: 900px) {
148
+ /* the rail's panel colour runs the full height, however tall the top bar is */
149
+ .body { grid-template-columns: 360px minmax(0, 1fr); background: linear-gradient(90deg, var(--panel) 359px, var(--line) 359px 360px, transparent 360px); }
150
+ .body.range { grid-template-columns: 400px minmax(0, 1fr); background: linear-gradient(90deg, var(--panel) 399px, var(--line) 399px 400px, transparent 400px); }
151
+ /* the bar stays put and the rail fits in what is left (--top-h is measured by the script) */
152
+ .top { position: sticky; top: 0; z-index: 5; }
153
+ .rail { position: sticky; top: var(--top-h, 0px); max-height: calc(100vh - var(--top-h, 0px)); overflow: auto; overscroll-behavior: contain; border-bottom: 0; border-right: 1px solid var(--line); }
128
154
  }
155
+ .rail { background: var(--panel); border-bottom: 1px solid var(--line); display: flex; flex-direction: column; min-width: 0; }
156
+ .main { min-width: 0; padding: 20px 16px 48px; }
157
+ @media (min-width: 900px) { .main { padding: 28px 40px 48px; } }
158
+
159
+ /* Attention: the card in the rail, the large one in a commit overview */
160
+ .att { margin: 20px 0 0; padding: 16px 18px; border: 1.5px solid var(--lv); border-radius: 12px; background: var(--panel); max-width: 720px; --lv: var(--attention-none); }
161
+ .att .lvl { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; font-size: 22px; font-weight: 600; letter-spacing: 0.02em; color: var(--lv); }
162
+ .att .src { font-size: 12px; font-weight: 500; letter-spacing: 0; color: var(--muted); border: 1px solid var(--line); border-radius: 999px; padding: 0 8px; }
163
+ .att ul { margin: 10px 0 0; padding-left: 20px; }
164
+ .att li { margin-bottom: 4px; }
165
+ .att .look { margin: 10px 0 0; font-size: 13px; }
166
+ .att .override { margin: 10px 0 0; font-size: 13px; color: var(--warn-text); background: var(--warn-bg); border-radius: 6px; padding: 6px 10px; }
167
+ .att .timedout { margin: 10px 0 0; padding: 6px 10px 6px 28px; font-size: 13px; color: var(--warn-text); background: var(--warn-bg); border-radius: 6px; }
168
+ .att .override.mismatch { font-weight: 600; border: 1px solid var(--warn-border); }
169
+ .gauge { display: inline-flex; gap: 3px; }
170
+ .gauge i { display: block; width: 22px; height: 8px; border-radius: 2px; background: var(--line); }
171
+ .gauge i.on { background: var(--lv); }
172
+ .gauge.small i { width: 12px; height: 6px; }
173
+
174
+ /* Impact meter: five bars that grow, filled up to the level */
175
+ .meter { display: inline-flex; align-items: flex-end; gap: 2px; height: 16px; flex: none; }
176
+ .meter i { display: block; width: 4px; border-radius: 1px; background: var(--line); }
177
+ .meter i.on { background: var(--m, var(--impact-local)); }
178
+ .meter i:nth-child(1) { height: 6px; }
179
+ .meter i:nth-child(2) { height: 9px; }
180
+ .meter i:nth-child(3) { height: 11px; }
181
+ .meter i:nth-child(4) { height: 14px; }
182
+ .meter i:nth-child(5) { height: 16px; }
183
+ .meter.unrated i { background: transparent; border: 1px dashed var(--muted); }
184
+
185
+ /* Rail: groups */
186
+ .rail h2 { margin: 0; }
187
+ .railhead { padding: 4px 24px 8px; }
188
+ .necs { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 16px 10px; }
189
+ .chip { display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--line); border-radius: 999px; padding: 2px 10px; font-size: 13px; background: var(--panel); color: var(--ink); }
190
+ button.chip { min-height: 32px; }
129
191
  .chip .count { color: var(--muted); font-variant-numeric: tabular-nums; }
130
- .chip[aria-pressed="false"] { opacity: 0.45; }
131
- .chip[aria-pressed="false"] .dot { background: transparent; border: 1.5px solid var(--c); }
132
- .overview .note { color: var(--muted); font-size: 12.5px; margin-top: 8px; }
133
-
134
- .toolbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; margin: 24px 0 12px; }
135
- .toolbar .shown { color: var(--muted); font-size: 13px; }
136
- .btn {
137
- border: 1px solid var(--border); background: var(--surface); color: var(--text);
138
- border-radius: 6px; padding: 4px 10px; font: inherit; font-size: 13px; cursor: pointer;
139
- }
140
- .btn:hover { background: var(--surface-2); }
192
+ .chip[aria-pressed="false"] { opacity: 0.6; text-decoration: line-through; }
193
+ .chip.link { cursor: pointer; }
194
+ .chip.link:hover { background: var(--accent-soft); }
195
+ .rows { display: flex; flex-direction: column; padding: 0 8px; }
196
+ .row { display: flex; align-items: stretch; border-radius: 8px; border: 1px solid transparent; }
197
+ .row.sel { background: var(--accent-soft); border-color: var(--accent); }
198
+ .row.done .rtitle { color: var(--muted); }
199
+ .check { flex: 0 0 44px; min-height: 44px; border: 0; background: transparent; display: flex; align-items: center; justify-content: center; }
200
+ .check span { width: 18px; height: 18px; border: 1.5px solid var(--muted); border-radius: 5px; display: flex; align-items: center; justify-content: center; background: var(--panel); font-size: 13px; line-height: 1; }
201
+ .check[aria-pressed="true"] span { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
202
+ .pick { flex: 1; min-width: 0; border: 0; background: transparent; text-align: left; display: flex; align-items: center; gap: 10px; padding: 8px 8px 8px 0; min-height: 56px; }
203
+ .rtxt { display: flex; flex-direction: column; min-width: 0; flex: 1; }
204
+ .rtitle { font-weight: 500; line-height: 1.3; overflow-wrap: anywhere; }
205
+ .rsub { color: var(--muted); font-size: 12px; overflow-wrap: anywhere; }
206
+ .flag { flex: none; font-size: 11px; font-weight: 600; border: 1px solid var(--lv, var(--attention-careful)); color: var(--lv, var(--attention-careful)); border-radius: 4px; padding: 0 5px; }
207
+ .emptyrows { padding: 8px 24px 16px; color: var(--muted); }
208
+ .overviewrow { padding: 12px 8px 4px; }
209
+ .overviewrow .row .pick { min-height: 44px; padding-left: 16px; }
210
+ .railfoot { margin-top: auto; padding: 16px; border-top: 1px solid var(--line); font-size: 13px; }
211
+ .railfoot .eyebrow { margin-bottom: 6px; }
212
+ .railfoot a { display: inline-block; padding: 8px 0; }
141
213
 
142
- /* Group cards */
143
- .group {
144
- background: var(--surface); border: 1px solid var(--border); border-left: 4px solid var(--c);
145
- border-radius: 8px; margin: 0 0 12px; scroll-margin-top: 16px;
146
- }
147
- .group:focus-visible, .group.focused { outline: 2px solid var(--accent); outline-offset: 2px; }
148
- .group.hidden { display: none; }
149
- .group-head { padding: 14px 16px 4px; }
150
- .group-title { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; }
151
- .badge {
152
- font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;
153
- color: var(--c); border: 1px solid var(--c); border-radius: 4px; padding: 0 5px;
154
- }
155
- .intent { font-weight: 600; font-size: 16px; overflow-wrap: anywhere; }
156
- .gid { font-family: var(--mono); font-size: 12.5px; color: var(--muted); }
157
- .stats { margin-left: auto; font-family: var(--mono); font-size: 12.5px; color: var(--muted); white-space: nowrap; }
214
+ /* Rail: commits of a range */
215
+ .sum { margin: 16px; padding: 14px 16px; border: 1px solid var(--line); border-radius: 10px; background: var(--bg); }
216
+ .sum .line { margin-top: 8px; color: var(--muted); font-size: 13px; }
217
+ .sum .line b { color: var(--ink); font-weight: 600; }
218
+ .strip { display: flex; gap: 3px; height: 14px; margin-top: 10px; }
219
+ .strip i { display: block; flex: 1; border-radius: 3px; background: var(--lv); min-width: 2px; }
220
+ .strip i.none { background: transparent; border: 1.5px dashed var(--attention-none); }
221
+ .commits { display: flex; flex-direction: column; padding: 0 8px 16px; }
222
+ .crow { margin-bottom: 2px; }
223
+ .crow > .pick { display: block; width: 100%; border: 1px solid transparent; border-radius: 8px; padding: 10px 12px; min-height: 64px; }
224
+ .crow.sel > .pick { background: var(--accent-soft); border-color: var(--accent); }
225
+ .rtop { display: flex; align-items: center; gap: 10px; }
226
+ .rtop .sha { border: 0; background: transparent; padding: 0; color: var(--muted); }
227
+ .lv { margin-left: auto; display: inline-flex; align-items: center; gap: 8px; font-size: 11.5px; font-weight: 600; letter-spacing: 0.05em; color: var(--lv); }
228
+ .csubject { display: block; font-weight: 500; line-height: 1.3; margin-top: 2px; overflow-wrap: anywhere; }
229
+ .cmeta { display: block; color: var(--muted); font-size: 12px; margin-top: 2px; }
230
+ .crow .rows { padding: 2px 0 8px 12px; }
231
+ .mergelist { padding: 0 24px 16px; color: var(--muted); font-size: 13px; }
232
+ .mergelist .eyebrow { margin-bottom: 4px; }
233
+
234
+ /* Detail pane */
235
+ .crumbs { color: var(--muted); font-size: 13px; }
236
+ .commit { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 12px; color: var(--muted); font-size: 13px; margin-bottom: 8px; }
237
+ h1 { font-size: 26px; line-height: 1.25; margin: 6px 0 14px; letter-spacing: -0.015em; font-weight: 600; max-width: 40ch; overflow-wrap: anywhere; }
238
+ h2 { font-size: 15px; margin: 28px 0 10px; }
239
+ .tags { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; }
240
+ .imp { display: inline-flex; align-items: center; gap: 8px; font-weight: 500; color: var(--m); }
241
+ .imp.derived { font-style: italic; }
242
+ .reason { margin: 10px 0 0; color: var(--muted); max-width: 70ch; overflow-wrap: anywhere; }
243
+ .stats { font-family: var(--mono); font-size: 12px; color: var(--muted); white-space: nowrap; }
158
244
  .plus { color: var(--add-mark); }
159
245
  .minus { color: var(--del-mark); }
160
246
  .stale { font-size: 12px; color: var(--warn-text); background: var(--warn-bg); border-radius: 4px; padding: 0 6px; }
161
-
162
- .facts { margin: 8px 0 0; padding: 0 16px 12px; display: grid; grid-template-columns: max-content 1fr; gap: 4px 12px; font-size: 14px; }
163
- .facts dt { color: var(--muted); font-size: 12.5px; padding-top: 1px; }
164
- .facts dd { margin: 0; max-width: 80ch; overflow-wrap: anywhere; }
165
- @media (max-width: 600px) {
166
- .facts { grid-template-columns: 1fr; }
167
- .facts dd { margin-bottom: 6px; }
168
- .stats { margin-left: 0; }
169
- }
170
-
171
- .code-toggle {
172
- display: block; width: 100%; text-align: left; border: 0; border-top: 1px solid var(--border);
173
- background: transparent; color: var(--muted); font: inherit; font-size: 13px; padding: 8px 16px; cursor: pointer;
174
- border-radius: 0 0 8px 0;
175
- }
176
- .code-toggle:hover { background: var(--surface-2); color: var(--text); }
177
- .code { border-top: 1px solid var(--border); padding: 12px 16px 16px; }
178
- .code[hidden] { display: none; }
247
+ .warning { margin: 16px 0 0; padding: 12px 14px; border-radius: 8px; max-width: 720px; background: var(--warn-bg); border: 1px solid var(--warn-border); color: var(--warn-text); }
248
+ .statement { margin: 12px 0 0; max-width: 75ch; overflow-wrap: anywhere; }
249
+ .statement .label { color: var(--muted); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; margin-right: 8px; }
250
+ .cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 12px; margin: 24px 0 8px; align-items: start; }
251
+ .card { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 14px 16px; min-width: 0; }
252
+ .card p { margin: 4px 0 0; overflow-wrap: anywhere; }
253
+ .files { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }
254
+ .frow { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding: 8px 16px; border-top: 1px solid var(--line); }
255
+ .frow:first-child { border-top: 0; }
256
+ .frow .mono { overflow-wrap: anywhere; min-width: 0; }
257
+ .chead { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; margin: 28px 0 10px; }
258
+ .chead h2 { margin: 0; }
259
+ .btn { display: inline-flex; align-items: center; border: 1px solid var(--line); background: var(--panel); border-radius: 8px; padding: 8px 16px; min-height: 44px; color: var(--ink); }
260
+ .btn:hover:not(:disabled) { background: var(--accent-soft); }
261
+ .btn.primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); font-weight: 500; }
262
+ .btn.primary:hover:not(:disabled) { background: var(--accent-strong); border-color: var(--accent-strong); }
263
+ .btn.small { min-height: 36px; padding: 4px 12px; font-size: 13px; }
264
+ .nav { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 28px; }
265
+ .keys { margin-left: auto; color: var(--muted); font-size: 12px; align-self: center; }
179
266
 
180
267
  /* Hunks */
181
- .hunk { border: 1px solid var(--border); border-radius: 6px; overflow: hidden; margin: 0 0 12px; }
182
- .hunk:last-child { margin-bottom: 0; }
183
- .hunk-head {
184
- display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px;
185
- padding: 6px 10px; background: var(--surface-2); border-bottom: 1px solid var(--border); font-size: 12.5px;
186
- }
187
- .hunk-head .file { font-family: var(--mono); font-weight: 600; overflow-wrap: anywhere; }
268
+ .hunk { margin-top: 12px; border: 1px solid var(--line); border-radius: 10px; overflow: hidden; background: var(--panel); }
269
+ .hunk-head { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; padding: 8px 14px; background: var(--bg); border-bottom: 1px solid var(--line); font-size: 12.5px; }
270
+ .hunk.closed .hunk-head { border-bottom: 0; }
271
+ .htoggle { display: inline-flex; align-items: center; gap: 8px; border: 0; background: transparent; padding: 6px 4px; min-height: 36px; text-align: left; border-radius: 6px; }
272
+ .htoggle:hover { background: var(--accent-soft); }
273
+ .hunk-head .file { font-family: var(--mono); font-weight: 500; overflow-wrap: anywhere; }
274
+ .chev { color: var(--muted); width: 1em; }
188
275
  .hunk-head .range { font-family: var(--mono); color: var(--muted); }
189
276
  .also { color: var(--muted); }
190
277
  .also a { font-family: var(--mono); }
191
- .scroll { overflow-x: auto; }
192
- table.diff { border-collapse: collapse; width: 100%; font-family: var(--mono); font-size: 12.5px; line-height: 1.5; }
278
+ .scroll { overflow-x: auto; overscroll-behavior-x: contain; }
279
+ table.diff { border-collapse: collapse; width: 100%; font-family: var(--mono); font-size: 12.5px; line-height: 1.6; }
193
280
  table.diff td { padding: 0 8px; vertical-align: top; }
194
- td.ln { width: 1%; min-width: 3.2em; text-align: right; color: var(--muted); user-select: none; opacity: 0.8; }
281
+ td.ln { width: 1%; min-width: 3.2em; text-align: right; color: var(--muted); user-select: none; }
195
282
  td.mark { width: 1%; padding: 0 2px 0 4px; user-select: none; }
196
283
  td.text { white-space: pre; }
197
284
  tr.add { background: var(--add-bg); }
@@ -199,31 +286,90 @@ tr.add td.mark { color: var(--add-mark); }
199
286
  tr.del { background: var(--del-bg); }
200
287
  tr.del td.mark { color: var(--del-mark); }
201
288
  tr.meta td { background: var(--meta-bg); color: var(--meta-text); }
202
- tr.gap td { background: var(--surface-2); color: var(--muted); font-style: italic; font-family: var(--sans); padding: 2px 8px; }
203
-
204
- /* Unannotated, alternatives, risks */
205
- .section { margin-top: 40px; }
206
- .section h2 { font-size: 17px; margin: 0 0 12px; }
207
- .unannotated { border: 1px dashed var(--del-mark); border-radius: 8px; padding: 12px 16px; background: var(--surface); }
208
- .list { margin: 0; padding-left: 20px; max-width: 80ch; }
209
- .list li { margin-bottom: 6px; }
289
+ tr.gap td { background: var(--bg); color: var(--muted); font-style: italic; font-family: var(--sans); padding: 2px 8px; }
290
+
291
+ /* A commit's groups in the detail pane (range pages) */
292
+ .filters { margin: 12px 0 0; }
293
+ .main .necs { padding: 10px 0; }
294
+ .main .rows { padding: 8px; background: var(--panel); border: 1px solid var(--line); border-radius: 10px; }
295
+ .back { margin-bottom: 12px; max-width: 100%; text-align: left; }
296
+
297
+ /* Overview extras */
298
+ .bar { display: flex; height: 10px; border-radius: 5px; overflow: hidden; background: var(--line); max-width: 720px; }
299
+ .bar span { display: block; height: 100%; background: var(--c); min-width: 2px; }
300
+ .bar span + span { border-left: 2px solid var(--panel); }
301
+ .legend { display: flex; flex-wrap: wrap; gap: 4px 14px; margin-top: 8px; font-size: 12.5px; color: var(--muted); }
302
+ .legend .dot { margin-right: 6px; }
303
+ .dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; flex: none; background: var(--c, var(--muted)); }
304
+ .legend + .chips { margin-top: 12px; }
305
+ .chips { display: flex; flex-wrap: wrap; gap: 6px; }
306
+ .chips .chip b { font-family: var(--mono); font-weight: 500; font-size: 12px; }
307
+ .chips .chip span { color: var(--muted); }
308
+ .warnbox { margin: 20px 0 0; padding: 16px 18px; border: 1px dashed var(--warn); border-radius: 12px; background: var(--panel); max-width: 720px; }
309
+ .warnbox .t { font-weight: 600; }
310
+ .warnbox p { margin: 6px 0 0; }
311
+ .cmd { font-family: var(--mono); font-size: 12px; color: var(--muted); }
312
+ .unannotated { border: 1px dashed var(--del-mark); border-radius: 10px; padding: 12px 16px; background: var(--panel); }
313
+ .unannotated > p { margin: 0 0 4px; }
314
+ .list { margin: 0; padding-left: 20px; max-width: 70ch; }
315
+ .list li { margin-bottom: 6px; overflow-wrap: anywhere; }
210
316
  .list .why { color: var(--muted); }
211
317
 
212
- footer { margin-top: 48px; color: var(--muted); font-size: 12.5px; }
213
- kbd { font-family: var(--mono); font-size: 11.5px; border: 1px solid var(--border); border-bottom-width: 2px; border-radius: 4px; padding: 0 4px; background: var(--surface); }
214
-
215
- /* Range pages */
216
- .commit-list { list-style: none; margin: 0 0 8px; padding: 0; font-size: 14px; }
217
- .commit-list li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; padding: 3px 0; }
218
- .commit-list .sha { font-size: 12px; }
219
- .commit-section { margin-top: 48px; padding-top: 24px; border-top: 1px solid var(--border); scroll-margin-top: 16px; }
220
- .commit-section h2 { font-size: 20px; margin: 8px 0 12px; }
221
- .commit-section .section h3 { font-size: 16px; margin: 0 0 12px; }
222
- .toc h2.commit-head { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-transform: none; letter-spacing: 0; font-family: var(--mono); }
223
-
224
- /* Reviews recorded for a commit */
225
- .reviews { margin: 8px 0 12px; display: flex; flex-direction: column; gap: 4px; }
226
- .review summary { cursor: pointer; }
318
+ /* Recorded reviews: a compact status block in the rail, the full view in the pane */
319
+ .reviews { display: flex; flex-direction: column; gap: 2px; }
320
+ .rvrow { display: flex; align-items: center; gap: 8px; width: 100%; min-height: 44px; padding: 4px 8px; border: 0; border-radius: 8px; background: transparent; text-align: left; }
321
+ .rvrow:hover { background: var(--accent-soft); }
322
+ .rvdot.ok { color: var(--ok); }
323
+ .rvdot.warn { color: var(--warn); }
324
+ .rvdot.none { color: var(--muted); }
325
+ .rvdot { width: 8px; height: 8px; border-radius: 50%; flex: none; background: currentColor; }
326
+ .rvstage { font-weight: 600; }
327
+ .rvtxt { color: var(--muted); font-size: 12.5px; flex: 1; min-width: 0; overflow-wrap: anywhere; }
328
+ .rvn { color: var(--muted); font-variant-numeric: tabular-nums; flex: none; }
227
329
  .review-ok { color: var(--ok); font-weight: 600; }
228
- .review-none { color: var(--muted); font-size: 0.9em; }
229
- .findings { white-space: pre-wrap; font-size: 0.85em; max-height: 32em; overflow: auto; padding: 8px; border-left: 3px solid var(--border); }
330
+ .review-none { color: var(--muted); }
331
+ .rvstagepane { margin-top: 28px; }
332
+ .rvstagepane h2 { margin: 0 0 6px; font-size: 18px; }
333
+ .rvsum { margin: 0 0 12px; }
334
+ .rvsum .stale { background: transparent; padding: 0; font-size: inherit; color: var(--warn); font-weight: 600; }
335
+ .entry { margin: 0 0 20px; max-width: 75ch; }
336
+ .entry.old { border-top: 1px solid var(--line); padding-top: 8px; }
337
+ .entry summary { cursor: pointer; padding: 6px 0; min-height: 36px; }
338
+ .entry summary .ehead { display: inline-flex; }
339
+ .ehead { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; margin-bottom: 8px; }
340
+ .chip.good { color: var(--ok); border-color: var(--ok); }
341
+ .chip.bad { color: var(--warn); border-color: var(--warn); }
342
+
343
+ /* Rendered markdown */
344
+ .md { max-width: 75ch; overflow-wrap: anywhere; }
345
+ .md p { margin: 0 0 10px; }
346
+ .md h3, .md h4, .md h5, .md h6 { margin: 18px 0 6px; line-height: 1.3; }
347
+ .md h3 { font-size: 17px; }
348
+ .md h4 { font-size: 15px; }
349
+ .md h5, .md h6 { font-size: 14px; }
350
+ .md ul, .md ol { margin: 0 0 10px; padding-left: 22px; }
351
+ .md li { margin-bottom: 4px; }
352
+ .md code { background: var(--bg); border: 1px solid var(--line); border-radius: 4px; padding: 0 4px; }
353
+ .md pre { margin: 0 0 12px; padding: 10px 12px; background: var(--bg); border: 1px solid var(--line); border-radius: 8px; overflow-x: auto; overscroll-behavior-x: contain; }
354
+ .md pre code { border: 0; padding: 0; background: transparent; white-space: pre; overflow-wrap: normal; }
355
+
356
+ footer { padding: 16px; color: var(--muted); font-size: 12.5px; border-top: 1px solid var(--line); }
357
+ footer p { margin: 6px 0 0; }
358
+
359
+ @media (max-width: 600px) {
360
+ h1 { font-size: 22px; }
361
+ .keys { display: none; }
362
+ .track { width: 80px; }
363
+ }
364
+ main footer { padding-left: 0; padding-right: 0; margin-top: 32px; }
365
+ .linkbtn { border: 0; background: transparent; padding: 8px 0; min-height: 36px; color: var(--accent); text-decoration: underline; text-align: left; }
366
+
367
+ .railsec { margin-top: 12px; padding: 12px 16px 4px; border-top: 1px solid var(--line); font-size: 13px; }
368
+ .railsec .eyebrow { margin-bottom: 4px; }
369
+ .overviewrow .lv { margin-left: 8px; flex: none; }
370
+ .focuscards { display: flex; flex-direction: column; gap: 8px; max-width: 720px; }
371
+ .focuscard { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; width: 100%; text-align: left; padding: 12px 16px; min-height: 44px; border: 1px solid var(--line); border-radius: 10px; background: var(--panel); }
372
+ .focuscard:hover { background: var(--accent-soft); border-color: var(--accent); }
373
+ .fctitle { font-weight: 600; overflow-wrap: anywhere; }
374
+ .fcmeta { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; }
375
+ .fcreason { color: var(--muted); overflow-wrap: anywhere; }