@fullstackhouse/cerber 0.18.1 โ†’ 0.19.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/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # cerber ๐Ÿ•
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/%40fullstackhouse%2Fcerber)](https://www.npmjs.com/package/@fullstackhouse/cerber)
4
+ [![license](https://img.shields.io/badge/license-MIT-blue)](./LICENSE)
5
+
3
6
  **AI code-review cockpit.** Claude reviews your pull requests into local artifacts โ€”
4
7
  a summary, a chaptered walkthrough of the changes, draft inline comments, and a
5
8
  verdict with a confidence score. You go through it in a local web cockpit.
@@ -10,6 +13,11 @@ reviewing is 100% local and read-only.
10
13
 
11
14
  Named after Cerberus, the gatekeeper: cerber guards what gets merged.
12
15
 
16
+ ![The queue: three drafted reviews, each with a verdict and confidence in its row; under the cursor, a strip quotes the selected review's own reasoning so it can be judged without opening it.](docs/queue.png)
17
+
18
+ *Every screenshot in this README is cerber reviewing cerber's own pull
19
+ requests.*
20
+
13
21
  ## Quick start
14
22
 
15
23
  Requires Node 20+, an authenticated [`gh`](https://cli.github.com/) and a
@@ -36,19 +44,24 @@ Sending stays a human click. Tame it with `--no-auto-review` (list awaiting
36
44
  PRs, review on click) or `--no-poll` (no GitHub polling at all) โ€” or flip the
37
45
  same two switches in the cockpit's Settings, which persist in
38
46
  `~/.cerber/config.json` and apply on the next poll. The queue only lists what
39
- still wants you: anything you have settled drops into a drawer under it โ€” one
40
- for reviews you sent, one for the ones you marked reviewed or skipped, one for
41
- merged and closed PRs โ€” and PRs in archived repos are never picked up at all,
42
- since the repo is read-only and a review could never be sent.
47
+ still wants you: anything you have settled moves to the filed tabs right of
48
+ the thin rule in the filter row โ€” reviews you sent, ones you marked reviewed
49
+ or skipped, merged and closed PRs โ€” each tab appearing only while it holds
50
+ something. PRs in archived repos are never picked up at all, since the repo
51
+ is read-only and a review could never be sent.
52
+
53
+ ### Settled means you settled it
43
54
 
44
55
  Settling a review is your decision, not GitHub's: skip a PR, or mark one
45
56
  reviewed without sending, and GitHub still holds its review request open,
46
57
  because nothing cerber does here reaches it. Those rows stay marked where they
47
- sit, counted on the drawer they went into, and named in place of the empty
58
+ sit, counted on the tab they went into, and named in place of the empty
48
59
  inbox โ€” cerber never tells you nothing awaits you while its own poll says
49
60
  otherwise. It is a report, not a nag: what you decided here stands, and the
50
61
  mark clears itself the moment GitHub hears from you.
51
62
 
63
+ ### Whose move is it
64
+
52
65
  An open request doesn't mean the ball is yours, though. You may have argued the
53
66
  whole thing out in the PR conversation and never pressed GitHub's review
54
67
  button, which leaves the request open with nothing blocked on you. So cerber
@@ -59,6 +72,8 @@ every PR back on you. If the conversation can't be read it says `unanswered on
59
72
  GitHub` and claims nothing further โ€” guessing "you never replied" at someone
60
73
  who did is the one mistake worth designing against.
61
74
 
75
+ ### It taps you on the shoulder
76
+
62
77
  The cockpit is a background tab most of the day, so it tells you when the queue
63
78
  grows: a desktop notification naming the PR that arrived โ€” click it to open the
64
79
  review โ€” folded into one popup when several land at once. It stays quiet while
@@ -86,8 +101,22 @@ Each review is a plain JSON artifact in `~/.cerber/reviews/` (override with
86
101
  - **Run metadata** โ€” model, whether it read the source or only the diff, and
87
102
  the token spend as an API-rate equivalent
88
103
 
104
+ ![A review opened in the cockpit: the verdict pill by the PR title, a walkthrough rail listing chapters and their comments on the left, and a summary written in sections โ€” problem first, then the fix, then details.](docs/walkthrough.png)
105
+
89
106
  The cockpit (`cerber serve`) renders the queue and the per-PR walkthrough with
90
- diffs. Artifacts are plain JSON you can `cat`, edit, or pipe into anything.
107
+ diffs. Draft comments sit inline in the diff, anchored to the line they're
108
+ about, each waiting for you to keep, rewrite, or drop it:
109
+
110
+ ![A chapter of the walkthrough: the diff with a draft comment card anchored under the line it discusses, explaining the finding in plain words.](docs/comment.png)
111
+
112
+ Any line of the diff is one click away from being talked about: hover it and a
113
+ `+` appears in the gutter, the way it does on GitHub. What opens is one box
114
+ with two exits โ€” write the comment yourself, or ask the reviewer about that
115
+ line and get the answer in the conversation below. A line the PR removes can be
116
+ asked about too; a comment on one posts on the file, since GitHub only takes
117
+ inline comments on the new side of a diff.
118
+
119
+ Artifacts are plain JSON you can `cat`, edit, or pipe into anything.
91
120
 
92
121
  The queue is meant to be walked, not clicked through: `j`/`k` move the cursor
93
122
  and the strip under the table explains whatever it lands on โ€” the verdict's own
@@ -249,6 +278,14 @@ The reviewer revises the draft as it answers: there is no accept step, because
249
278
  you already asked for the change. What it changed shows up in the transcript
250
279
  (*"โœŽ rewrote the summary"*, *"โœŽ dropped the comment on line 250"*).
251
280
 
281
+ ![Talking to the reviewer: pushed back on a nit, it re-checks the code, corrects the push-back's own claim, drops the comment anyway โ€” and the revision is noted right in the transcript.](docs/chat.png)
282
+
283
+ That conversation is real: pushed back on a nit, the reviewer re-checked the
284
+ code, corrected the push-back's own claim about the rename's size โ€” and still
285
+ dropped the comment, because a nit hanging off a PR with two real findings
286
+ just dilutes them. The rail counts *2 keeping ยท 1 dropped*, and nothing about
287
+ the exchange went anywhere near GitHub.
288
+
252
289
  - **It is the same reviewer.** A turn resumes the Claude session the review ran
253
290
  in, so it still holds everything it read. Asking "did you actually check
254
291
  that?" gets an answer from the run that did or didn't โ€” not from a fresh
@@ -274,7 +311,10 @@ you already asked for the change. What it changed shows up in the transcript
274
311
  will tell you one of them needs changing rather than rewriting it. Say so
275
312
  explicitly and it will.
276
313
  - **Point at things.** *discuss* on any comment, chapter, or the summary drops
277
- a reference into your next message.
314
+ a reference into your next message. From the diff's own gutter, *ask the
315
+ reviewer* points at one line and sends the question there and then โ€” the
316
+ line comes along quoted, so it answers about that line rather than counting
317
+ rows.
278
318
  - **Reset** puts the review back the way the AI first wrote it and keeps the
279
319
  conversation โ€” you lose the edits, not the reasoning.
280
320
 
@@ -283,24 +323,13 @@ the summary, comments and verdict alone, and is still one deliberate click. A
283
323
  review that has already been sent can't be argued with: that artifact is the
284
324
  record of what GitHub has.
285
325
 
286
- ## Status / roadmap
287
-
288
- Early, but all six planned phases have shipped.
289
-
290
- 1. โœ… `cerber review <pr>` + artifact schema + cockpit (read-only)
291
- 2. โœ… Comment editing in the cockpit (edit/approve/drop/add), verdict override,
292
- gated **Send** (one deliberate click; posts the review via your `gh` โ€” the
293
- only GitHub write in the codebase) + `cerber export` to markdown
294
- 3. โœ… `--awaiting-me` discovery, parallel runs (`--parallel`, default 3),
295
- freshness skip (same head SHA โ†’ no re-run; `--force` overrides), queue stats
296
- 4. โœ… Inbox mode (now the default): `cerber serve` polls for PRs awaiting your
297
- review and reviews them automatically; token auth for non-localhost binds
298
- 5. โœ… Confidence calibration (`cerber stats`), shadow-mode auto-send (default
299
- in daemon: logs what would be sent), opt-in `--auto-send` (approve-only,
300
- `--auto-send-threshold`, default 90%)
301
- 6. โœ… Reviews keep up with the PR: opening one re-anchors its comments onto new
302
- commits, flags the ones whose code is gone, and offers a re-review that
303
- keeps everything you wrote
326
+ ## Status
327
+
328
+ Early, but whole: everything described above has shipped โ€” reviewing,
329
+ editing and the gated Send, inbox discovery with parallel runs, confidence
330
+ calibration (`cerber stats`), shadow-mode and opt-in auto-send, re-anchoring
331
+ onto new commits, source-backed and trusted runs, and the reviewer chat.
332
+ `cerber export` writes a review out as markdown if you want it elsewhere.
304
333
 
305
334
  ## Running on a VPS
306
335
 
@@ -153,8 +153,15 @@ export const ChatTurnSchema = z.object({
153
153
  /** What the user pointed at with "discuss this" โ€” user turns only. */
154
154
  refs: z
155
155
  .array(z.object({
156
- target: z.enum(["summary", "verdict", "chapter", "comment"]),
156
+ target: z.enum(["summary", "verdict", "chapter", "comment", "line"]),
157
157
  id: z.string().nullable().default(null),
158
+ /** A "line" ref: the file, and the line as numbered on `side`. */
159
+ path: z.string().nullish(),
160
+ line: z.number().nullish(),
161
+ /** Which column of the diff the number came from โ€” a removed line is
162
+ only numbered on the old side, and can still be asked about.
163
+ Absent means the new side. */
164
+ side: z.enum(["new", "old"]).optional(),
158
165
  }))
159
166
  .default([]),
160
167
  /** Edits this turn made to the review. Assistant turns only. */
package/dist/core/diff.js CHANGED
@@ -47,17 +47,29 @@ export function patchForFiles(diff, files) {
47
47
  * lines inside hunks โ€” exactly the lines a GitHub comment can attach to.
48
48
  */
49
49
  export function newSideLineText(diff) {
50
+ return sideLineText(diff, "new");
51
+ }
52
+ /**
53
+ * Old-side lines with their text, per file: removed and context lines. Nothing
54
+ * can be posted against these โ€” they only exist to quote a line the PR took
55
+ * out, which is a fair thing to ask the reviewer about.
56
+ */
57
+ export function oldSideLineText(diff) {
58
+ return sideLineText(diff, "old");
59
+ }
60
+ function sideLineText(diff, side) {
61
+ const own = side === "new" ? "+" : "-";
50
62
  const result = new Map();
51
63
  for (const { path, patch } of splitDiffByFile(diff)) {
52
64
  const lines = new Map();
53
- let newLine = 0;
65
+ let n = 0;
54
66
  let inHunk = false;
55
67
  const patchLines = patch.split("\n");
56
68
  for (let i = 0; i < patchLines.length; i++) {
57
69
  const line = patchLines[i];
58
- const hunk = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
70
+ const hunk = line.match(/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
59
71
  if (hunk) {
60
- newLine = Number(hunk[1]);
72
+ n = Number(side === "new" ? hunk[2] : hunk[1]);
61
73
  inHunk = true;
62
74
  continue;
63
75
  }
@@ -67,11 +79,11 @@ export function newSideLineText(diff) {
67
79
  continue; // trailing split artifact
68
80
  if (line.startsWith("\\"))
69
81
  continue; // ""
70
- if (line.startsWith("+") || line.startsWith(" ") || line === "") {
71
- lines.set(newLine, line === "" ? "" : line.slice(1));
72
- newLine++;
82
+ if (line.startsWith(own) || line.startsWith(" ") || line === "") {
83
+ lines.set(n, line === "" ? "" : line.slice(1));
84
+ n++;
73
85
  }
74
- // "-" lines belong to the old side only.
86
+ // The other side's lines don't exist on this one.
75
87
  }
76
88
  result.set(path, lines);
77
89
  }
@@ -1,3 +1,4 @@
1
+ import { newSideLineText, oldSideLineText } from "../core/diff.js";
1
2
  /** Keep the prompt within a sane context budget. */
2
3
  export const MAX_DIFF_CHARS = 300_000;
3
4
  /** Told to the model only when a checkout backs it up. */
@@ -126,6 +127,17 @@ ${diffBody}
126
127
  function describeRefs(artifact, refs) {
127
128
  if (refs.length === 0)
128
129
  return "";
130
+ // Quoting a line means walking the whole diff, so each side is walked at
131
+ // most once per turn โ€” and not at all unless a line ref asks for it.
132
+ const walked = new Map();
133
+ const sideText = (side) => {
134
+ const already = walked.get(side);
135
+ if (already)
136
+ return already;
137
+ const text = (side === "old" ? oldSideLineText : newSideLineText)(artifact.diff);
138
+ walked.set(side, text);
139
+ return text;
140
+ };
129
141
  const lines = refs.map((ref) => {
130
142
  if (ref.target === "summary")
131
143
  return "- the summary";
@@ -135,6 +147,8 @@ function describeRefs(artifact, refs) {
135
147
  const ch = artifact.chapters.find((c) => c.id === ref.id);
136
148
  return `- the chapter "${ch?.title ?? ref.id}" (chapterId: ${ref.id})`;
137
149
  }
150
+ if (ref.target === "line")
151
+ return describeLineRef(ref, sideText);
138
152
  const c = artifact.comments.find((x) => x.id === ref.id);
139
153
  return c
140
154
  ? `- the comment on ${c.path}:${c.line ?? "file"} (commentId: ${c.id})`
@@ -142,6 +156,20 @@ function describeRefs(artifact, refs) {
142
156
  });
143
157
  return `\n## What the user is pointing at\n\n${lines.join("\n")}\n`;
144
158
  }
159
+ /**
160
+ * A line of the diff the user clicked, quoted back so the model is looking at
161
+ * the same thing they are โ€” a number alone would make it count rows.
162
+ */
163
+ function describeLineRef(ref, sideText) {
164
+ if (ref.path == null || ref.line == null)
165
+ return "- a line of the diff";
166
+ const removed = ref.side === "old";
167
+ const where = `${ref.path}:${ref.line}${removed ? " (a line this PR removes)" : ""}`;
168
+ const text = sideText(removed ? "old" : "new")
169
+ .get(ref.path)
170
+ ?.get(ref.line);
171
+ return text?.trim() ? `- ${where} โ€” \`${text.trim()}\`` : `- ${where}`;
172
+ }
145
173
  /** The review as it stands right now, with the ids a revision has to name. */
146
174
  function renderReview(artifact) {
147
175
  const chapters = artifact.chapters
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fullstackhouse/cerber",
3
- "version": "0.18.1",
3
+ "version": "0.19.0",
4
4
  "description": "AI code-review cockpit: Claude reviews your PRs into local artifacts; nothing reaches GitHub until you say so.",
5
5
  "license": "MIT",
6
6
  "author": "Full Stack House (https://fullstack.house)",
@@ -7,4 +7,4 @@
7
7
 
8
8
  Outdated base version: https://github.com/primer/github-syntax-light
9
9
  Current colors taken from GitHub's CSS
10
- */.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-variable,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id{color:#005cc5}.hljs-regexp,.hljs-string,.hljs-meta .hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-comment,.hljs-code,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-weight:700}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0}:root{--bg: #fcfcfd;--surface: #ffffff;--fg: #14171a;--fg-2: #3a3f45;--fg-3: #4a5058;--muted: #6b7178;--faint: #9aa0a8;--fainter: #b6bbc1;--line: #e6e8eb;--line-2: #dcdfe4;--line-3: #eef0f2;--accent: #3d4fd6;--accent-dark: #2c3aa8;--accent-wash: #f4f5ff;--green: #1a7f5a;--amber: #a06a00;--red: #c2314a;--mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;--prose: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Helvetica, sans-serif;--measure: 1560px}*{box-sizing:border-box}body{margin:0;font-family:var(--mono);font-size:12px;color:var(--fg);background:var(--bg)}a{color:var(--accent)}a:hover{color:var(--accent-dark)}h1,h2,h3{font-size:13px;font-weight:700;margin:0}.app{padding-bottom:48px}.wrap{max-width:var(--measure);margin:0 auto;padding:0 40px}.bar-inner{max-width:var(--measure);margin:0 auto;padding:0 40px;display:flex;align-items:center;gap:14px}.grow{flex-grow:1}.pad{padding:24px 40px}.muted,.faint{color:var(--faint)}.warn{color:var(--amber)}.danger{color:var(--red)}.add{color:var(--green)}.del{color:var(--red)}.icon{flex-shrink:0}.lab{font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--faint)}.key{font-size:10px;border:1px solid var(--line-2);border-bottom-width:2px;border-radius:3px;padding:1px 5px;color:var(--muted);background:var(--surface)}.hint{display:inline-flex;gap:5px;align-items:center;font-size:10.5px;color:var(--faint);white-space:nowrap}.prose{font-family:var(--prose);font-size:13px;line-height:1.6;color:var(--fg-2);text-wrap:pretty;max-width:720px}.prose.lead{font-size:13.5px;line-height:1.62;color:var(--fg)}.error{color:var(--red);background:#c2314a0f;border:1px solid rgba(194,49,74,.3);padding:8px 12px;border-radius:4px;margin:10px 0}.empty{text-align:center;color:var(--faint);margin:64px auto;flex-direction:column}.empty pre{display:inline-block;background:var(--line-3);padding:8px 16px;border-radius:4px}button{font:inherit;cursor:pointer}.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;font-size:11px;color:var(--fg-3);border:1px solid var(--line-2);border-radius:4px;padding:6px 10px;background:var(--surface);white-space:nowrap;text-decoration:none}.btn:hover:not(:disabled){color:var(--fg);border-color:var(--fainter)}.btn:disabled{opacity:.5;cursor:default}.btn-sm{font-size:10.5px;padding:3px 8px;gap:5px}.btn-accent{color:var(--accent);border-color:#3d4fd659}.btn-keep-on{color:#fff;background:var(--green);border-color:var(--green)}.btn-drop-on{color:var(--red);border-color:#c2314a66}.btn-dark{color:#fff;background:var(--fg);border-color:var(--fg);font-size:11.5px;padding:7px 14px}.btn-dark:disabled{color:var(--faint);background:#f0f1f3;border-color:var(--line-2)}.btn-dashed{display:inline-flex;align-items:center;gap:5px;font-size:10.5px;color:var(--muted);border:1px dashed var(--line-2);border-radius:4px;padding:5px 10px;background:transparent;margin-top:10px}.btn-primary{display:flex;align-items:center;justify-content:center;gap:8px;height:40px;padding:0 14px;font-size:12px;font-weight:500;color:#fff;background:var(--accent);border:1px solid var(--accent);border-radius:4px}.btn-primary .key{border-color:#ffffff4d;color:#ffffffbf;background:transparent}.link{display:inline-flex;align-items:center;gap:5px;font:inherit;font-size:11px;color:var(--accent);background:none;border:none;padding:0;text-decoration:none}.link:hover:not(:disabled){color:var(--accent-dark)}.link:disabled{color:var(--faint);cursor:default}.topbar{border-bottom:1px solid var(--line);background:var(--surface)}.topbar .bar-inner{padding-top:14px;padding-bottom:14px}.brand{display:inline-flex;align-items:center;gap:7px;font-size:15px;font-weight:700;letter-spacing:-.02em;color:var(--fg);text-decoration:none}.brand .logo{color:var(--accent);flex-shrink:0}.tagline{font-size:11px;color:var(--faint)}.topbar-link{font-size:11px;color:var(--muted);text-decoration:none;border-left:1px solid var(--line);padding-left:14px;display:inline-flex;align-items:center}.topbar-link:hover{color:var(--fg)}.bell{background:none;border:0;border-left:1px solid var(--line);padding:0 0 0 14px;font-family:inherit;cursor:pointer}.bell-on,.bell-ask{color:var(--accent)}.bell-blocked{color:var(--fainter);cursor:help}.daemon-bar{border-bottom:1px solid var(--line);background:#f7f8fa;font-size:11px;color:var(--muted)}.daemon-bar .bar-inner{padding-top:8px;padding-bottom:8px;gap:18px;flex-wrap:wrap}.daemon-bar b{color:var(--fg);font-weight:400}.daemon-live{display:inline-flex;align-items:center;gap:6px;color:var(--fg)}.daemon-bar .warn{display:inline-flex;align-items:center;gap:6px}.dot{width:5px;height:5px;border-radius:999px;background:var(--green);display:inline-block}.dot-off{background:var(--fainter)}.tabs{display:flex;align-items:center;gap:2px;padding-top:14px}.tab{font-size:12px;padding:6px 12px;border:none;border-bottom:2px solid transparent;border-radius:4px 4px 0 0;background:none;color:var(--muted)}.tab:hover{color:var(--fg)}.tab-on{color:var(--fg);font-weight:500;border-bottom-color:var(--accent)}.tab-count{color:var(--fainter);margin-left:4px}.tab-on .tab-count{color:var(--accent)}.tab-split{width:1px;flex-shrink:0;align-self:stretch;margin:4px 10px 6px;background:var(--line-2)}.tab-filed{color:var(--faint)}.tab-filed:hover,.tab-filed.tab-on{color:var(--muted)}.tab-requests{color:var(--amber)}.tab-requests:hover,.tab-requests.tab-on{color:var(--amber);border-bottom-color:var(--amber)}.tab-requests .tab-count,.tab-requests.tab-on .tab-count{color:var(--amber)}.empty-tab{padding:22px 10px;font-size:12.5px;max-width:88ch}.empty-tab .btn{margin-top:10px}.table-head,.row{display:flex;gap:12px;align-items:center}.table-head{font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--faint);padding:12px 10px 7px;border-bottom:1px solid var(--line-2)}.row{font-size:12px;padding:8px 10px;cursor:pointer;border-bottom:1px solid var(--line-3);border-left:2px solid transparent}.row:hover{background:#f7f8fa}.row-on{background:var(--accent-wash);border-left-color:var(--accent)}.row-on .col-caret{color:var(--accent)}.row-on .col-title{font-weight:500}.tag-done{flex-shrink:0;color:var(--muted);background:var(--line-3)}.tag-awaiting{flex-shrink:0;color:var(--amber);background:#a06a001a}.pull{display:flex;align-items:center;gap:6px;position:relative;color:var(--muted)}.tabs .pull{margin-right:16px}.empty .pull{justify-content:center;margin-top:10px}.pull:hover,.pull:focus-within{color:var(--fg-3)}.pull-input{width:200px;max-width:100%;font:inherit;font-size:12px;padding:2px 0;color:var(--fg);background:transparent;border:none;border-bottom:1px solid transparent;transition:width .12s ease,border-color .12s ease}.pull-input:hover::placeholder{color:var(--fg)}.pull-input:focus{outline:none;width:320px;border-bottom-color:var(--accent)}.pull-input::placeholder{color:var(--muted);transition:color .12s ease}.btn-small{padding:2px 8px;font-size:10px}.pull-error{position:absolute;top:100%;right:0;z-index:5;margin-top:4px;max-width:360px;padding:5px 8px;font-size:11px;line-height:1.4;color:var(--red);background:var(--bg);border:1px solid var(--line-2);border-radius:4px;box-shadow:0 2px 8px #14171a14}.tag-draft{flex-shrink:0;color:var(--muted);background:var(--line-3)}.col-caret{width:12px;flex-shrink:0;color:transparent}.col-slug{width:184px;flex-shrink:0;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.col-title{flex-grow:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.col-author{width:104px;flex-shrink:0;color:var(--faint);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.col-verdict{width:136px;flex-shrink:0;text-align:right;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.col-comments{width:26px;flex-shrink:0;text-align:right;color:var(--muted)}.col-size{width:104px;flex-shrink:0;text-align:right;color:var(--muted)}.col-age{width:44px;flex-shrink:0;text-align:right;color:var(--faint)}.table-head .col-verdict,.table-head .col-slug,.table-head .col-author{color:var(--faint);font-weight:400}.cursor-strip{display:flex;gap:18px;align-items:flex-start;border:1px solid var(--line-2);border-top:none;border-radius:0 0 4px 4px;background:var(--surface);padding:14px 16px}.cursor-strip-body{flex-grow:1;min-width:0}.strip-prose{--strip-cap: 196px;--strip-fade: 46px;margin:6px 0 0;max-width:700px;max-height:var(--strip-cap);overflow:hidden}.strip-prose-clipped{-webkit-mask-image:linear-gradient(#000 calc(var(--strip-cap) - var(--strip-fade)),transparent var(--strip-cap));mask-image:linear-gradient(#000 calc(var(--strip-cap) - var(--strip-fade)),transparent var(--strip-cap))}.cursor-strip-body .md p{margin:5px 0}.cursor-strip-body .md ul,.cursor-strip-body .md ol{margin:5px 0;padding-left:17px}.cursor-strip-body .md li{margin:2px 0}.cursor-strip-body .md li::marker{color:var(--faint)}.cursor-strip-body .md strong{font-weight:600;color:var(--fg)}.cursor-strip-meta{font-size:10.5px;color:var(--faint);margin-top:8px}.cursor-strip-actions{display:flex;flex-direction:column;gap:6px;width:176px;flex-shrink:0}.cursor-strip-row{display:flex;gap:6px}.cursor-strip-row .btn{flex-grow:1;padding:7px 0}.review-top{position:sticky;top:0;z-index:20;background:var(--surface)}.crumb{border-bottom:1px solid var(--line);background:var(--surface);font-size:11.5px;color:var(--muted)}.crumb .bar-inner{padding-top:12px;padding-bottom:12px;gap:12px}.crumb a{text-decoration:none}.crumb-sep{color:var(--line-2)}.crumb-slug{color:var(--fg)}.crumb-next{font-size:11px;max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.walk{display:inline-flex;gap:2px}.walk-btn{display:inline-flex;align-items:center;justify-content:center;padding:4px 6px;border:1px solid var(--line-2);border-radius:3px;background:var(--surface);color:var(--fg-3)}.walk-btn:disabled{color:#d4d7db;border-color:var(--line-3);cursor:default}.review-head{border-bottom:1px solid var(--line);background:var(--surface)}.review-head .wrap{padding-top:20px;padding-bottom:16px}.review-head-top,.review-head-meta{display:flex;align-items:center;gap:14px}.review-head-top{margin-bottom:8px}.review-title{font-size:19px;font-weight:700;letter-spacing:-.025em;color:var(--fg);text-decoration:none}.review-title:hover{color:var(--accent)}.review-head-meta{font-size:11px;color:var(--muted);gap:10px;flex-wrap:wrap}.verdict-pick{display:inline-flex;align-items:center;gap:5px;font-size:10.5px;padding:4px 9px;border-radius:4px;border:1px solid transparent;background:none;color:var(--faint);white-space:nowrap}.verdict-pick:hover{color:var(--fg-3)}.verdict-pick-on{background:#f0f1f3;border-color:var(--line-2);font-weight:500}.review-body{display:grid;align-items:start;grid-template-columns:236px minmax(0,1fr);grid-template-areas:"rail main" "rail cockpit"}.rail{grid-area:rail}.main{grid-area:main}.cockpit{grid-area:cockpit;min-width:0;padding-bottom:18px}.rail{padding:22px 20px 22px 0;position:sticky;top:var(--top-h, 0px);z-index:5;max-height:calc(100vh - var(--top-h, 0px));overflow-y:auto}@media(min-width:1600px){.review{--measure: 2100px}.review-body{grid-template-columns:236px minmax(0,1fr) clamp(420px,34%,680px);grid-template-areas:"rail main cockpit"}.cockpit{position:sticky;top:var(--top-h, 0px);height:calc(100vh - var(--top-h, 0px));padding:22px 0 18px 22px;display:flex;flex-direction:column;justify-content:space-between;gap:10px}.cockpit .chat-panel{flex:0 1 auto;display:flex;flex-direction:column;min-height:120px;margin:0}.cockpit .chat-panel .card-body{display:flex;flex-direction:column;min-height:0}.cockpit .chat-log{flex:1 1 auto;min-height:0;overflow-y:auto}.cockpit .verdict-bar{margin-top:0}.cockpit-decide{flex:0 0 auto;max-height:65%;overflow-y:auto}}.chapter,.comment,.card-summary,.card-why,.chat-panel,.send-panel,.sent-strip,.verdict-bar{scroll-margin-top:calc(var(--top-h, 0px) + 12px)}.rail-lab{display:block;margin-top:22px}.rail-jumps{display:flex;flex-direction:column;align-items:flex-start;gap:7px}.rail .rail-jumps .lab{margin-bottom:0}.rail-lab-btn{display:block;width:100%;text-align:left;font:inherit;padding:0;background:none;border:none}.rail-lab-btn:hover{color:var(--fg)}.rail .lab{margin-bottom:10px}.rail-item{display:flex;align-items:center;gap:6px;width:100%;text-align:left;font:inherit;font-size:11.5px;line-height:1.5;padding:5px 0 5px 9px;margin-left:-11px;background:none;border:none;border-left:2px solid transparent;color:var(--muted)}.rail-item:hover{color:var(--fg)}.rail-item-on{border-left-color:var(--accent);color:var(--fg);font-weight:500}.rail-group{margin-bottom:2px}.rail-count{font-size:10.5px;color:var(--fainter)}.rail-comment{display:flex;align-items:center;gap:6px;width:100%;font:inherit;font-size:11px;text-align:left;padding:3px 0 3px 20px;margin-left:-11px;background:none;border:none;border-left:2px solid transparent;color:var(--muted)}.rail-comment:hover{color:var(--fg)}.rail-comment-loc{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rail-comment-dropped{opacity:.55}.rail-comment-dropped .rail-comment-loc{text-decoration:line-through}.rail-dot{font-size:8px}.comment-flash{animation:comment-flash 1.8s ease-out}@keyframes comment-flash{0%,60%{background:var(--accent-wash);box-shadow:inset 0 0 0 2px #3d4fd673}}.rail-facts{font-size:11.5px;line-height:1.85;color:var(--muted)}.rail-facts b{color:var(--fg);font-weight:400}.main{min-width:0;padding:22px 0 0}.card{border:1px solid var(--line);border-radius:4px;background:var(--surface);margin-bottom:10px}.card-head{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;padding:11px 16px;border-bottom:1px solid var(--line-3)}.card-head .faint{font-size:11px}.card-body{padding:14px 16px}.card-body .lab{display:block;margin-bottom:7px}.card-body .prose{margin:0}.card-body .btn{margin-top:12px}.card-why{margin-bottom:8px}.chapter{border:1px solid var(--line);border-radius:4px;background:var(--surface);margin-bottom:10px;overflow:hidden}.chapter-head{display:flex;align-items:center;gap:10px;padding:11px 16px;cursor:pointer;-webkit-user-select:none;user-select:none}.chapter-body{padding:14px 16px 16px;border-top:1px solid var(--line-3)}.chapter-meta{font-size:11px;font-weight:400}.chapter-explanation{margin:0 0 14px}.comment{border:1px solid var(--line);border-radius:4px;background:#fbfbfc;padding:12px 14px;margin:8px 0}.comment-dropped{opacity:.5}.comment-dropped .comment-body{text-decoration:line-through}.comment-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:6px}.comment-loc{font-size:10.5px;color:var(--faint)}.comment-body{font-size:13px;line-height:1.58;color:var(--fg);max-width:640px}.comment-edit,.add-comment textarea,.chat-input{width:100%;font-family:var(--mono);font-size:12.5px;line-height:1.55;color:var(--fg);padding:10px 12px;border:1px solid var(--line-2);border-radius:4px;background:var(--surface);resize:vertical}.add-comment{border:1px dashed var(--line-2);border-radius:4px;padding:10px 12px;margin:10px 0;display:flex;flex-direction:column;gap:8px}.add-comment-row{display:flex;gap:8px;align-items:center}.add-comment select,.add-comment input{font:inherit;font-size:11.5px;padding:5px 7px;border:1px solid var(--line-2);border-radius:4px;background:var(--surface)}.inline-comment-row td{padding:0;background:#fbfbfc;border-top:1px solid var(--line);border-bottom:1px solid var(--line);white-space:normal}.inline-comment-row .comment{margin:0;border:none;border-radius:0;padding-left:44px;max-width:none}.diff{margin-top:8px;font-size:11.5px}.diff .d2h-file-header,.diff .d2h-file-name{font-family:var(--mono);font-size:11px}.diff .hljs{display:inline;padding:0;background:transparent;color:inherit}.freshness{background:#a06a0012;border:1px solid rgba(160,106,0,.3);color:#57430a;padding:10px 12px;border-radius:4px;margin-bottom:10px;line-height:1.55;font-size:11.5px}.freshness code{background:#0000000f;padding:1px 4px;border-radius:3px}.freshness-running{background:#3d4fd60f;border-color:#3d4fd64d;color:#26307a}.freshness .btn{margin-left:8px}.chat-panel{margin:18px 0 10px}.chat-log{display:flex;flex-direction:column;gap:14px;margin-bottom:14px}.chat-turn{border-left:2px solid var(--line-2);padding:2px 0 2px 12px}.chat-turn-user{border-left-color:var(--accent)}.chat-turn-pending{border-left-style:dashed}.chat-turn-who{font-size:10.5px;color:var(--faint);margin-bottom:4px}.chat-turn-body{max-width:700px;color:var(--fg)}.chat-revision{font-size:10.5px;color:var(--green);margin-top:6px}.chat-refusal{font-size:10.5px;color:var(--amber);margin-top:6px}.chat-waiting{font-size:11.5px;color:var(--muted);margin-top:6px}.chat-progress{list-style:none;margin:0;padding:0}.chat-progress li{opacity:.55;padding:1px 0}.chat-progress li.now{opacity:1}.chat-progress li.now:after{content:"โ€ฆ"}.chat-failed{font-size:11.5px;color:var(--red);margin-top:6px}.chat-refs{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-bottom:8px}.chat-ref{font-size:10.5px;font-family:var(--mono);padding:3px 9px;border:1px solid var(--line-2);border-radius:999px;color:var(--muted);background:transparent}.chat-refs-pending .chat-ref{cursor:pointer;color:var(--accent);border-color:#3d4fd659}.chat-actions{display:flex;align-items:center;gap:12px;margin-top:10px}.send-panel{border:1px solid var(--line-2);border-radius:4px;background:#f7f8fa;padding:16px 18px}.verdict-bar+.send-panel{border-radius:0 0 4px 4px}.send-top{display:flex;align-items:baseline;gap:12px;margin-bottom:14px}.send-top .faint{font-size:11px}.send-top .link{text-decoration:underline;text-underline-offset:2px}.send-action{display:flex;align-items:center;gap:16px;flex-wrap:wrap}.send-btn{display:inline-flex;align-items:center;gap:10px;height:44px;padding:0 20px;font-size:13px;font-weight:500;color:#fff;border:none;border-radius:4px;box-shadow:0 1px 2px #14171a1f}.send-btn:disabled{opacity:.6;cursor:default}.send-btn .key{border-color:#ffffff4d;color:#fffc;background:transparent}.send-as{position:relative}.send-as-toggle{font:inherit;font-size:11.5px;color:var(--fg-3);background:none;border:none;padding:0}.send-as-toggle b{color:var(--fg);font-weight:500;border-bottom:1px dotted var(--fainter)}.send-as-why{font-size:10.5px;color:var(--faint);margin-top:3px}.menu{position:absolute;top:34px;left:0;z-index:10;background:var(--surface);border:1px solid var(--line-2);border-radius:4px;box-shadow:0 6px 18px #14171a17;padding:4px;min-width:190px}.menu-item{display:flex;align-items:center;gap:9px;width:100%;font:inherit;font-size:11.5px;text-align:left;padding:7px 9px;border:none;border-radius:3px;background:none;color:var(--fg-3)}.menu-item:hover{background:#f7f8fa}.menu-item-on{background:#f4f5f7;color:var(--fg)}.menu-check{font-size:10px;color:var(--green)}.verdict-bar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;border:1px solid var(--line-2);border-bottom:none;border-radius:4px 4px 0 0;background:var(--surface);padding:10px 18px;margin-top:18px}.verdict-bar .lab{margin-right:4px}.verdict-bar .faint{font-size:10.5px}.send-footer{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:16px;padding-top:14px;border-top:1px solid var(--line-2);font-size:10.5px}.body-preview{margin-top:14px;padding:14px 16px;background:var(--surface);border:1px solid var(--line);border-radius:4px;font-family:var(--prose);font-size:12.5px;line-height:1.62;color:var(--fg-2);white-space:pre-wrap;max-width:760px;max-height:420px;overflow:auto}.sent-strip{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;border:1px solid rgba(26,127,90,.4);background:#1a7f5a12;border-radius:4px;padding:14px 16px;font-size:11.5px}.sent-strip strong{color:var(--green);font-size:13px;white-space:nowrap}.sent-strip a{display:inline-flex;align-items:center;gap:5px;white-space:nowrap;text-decoration:none}.post-actions{display:flex;align-items:center;gap:12px;margin-top:12px;font-size:10.5px}.post-actions .link{font-size:10.5px;color:var(--muted)}.post-actions .link:hover{color:var(--fg)}.md>:first-child{margin-top:0}.md>:last-child{margin-bottom:0}.md p{margin:8px 0}.md code{font-family:var(--mono);font-size:.9em;background:#f0f2f5;padding:1px 5px;border-radius:3px}.md pre{background:#f7f8fa;border:1px solid var(--line);border-radius:4px;padding:10px 12px;overflow-x:auto}.md pre code{background:none;padding:0;font-size:11.5px}.md ul,.md ol{margin:8px 0;padding-left:24px}.md blockquote{margin:8px 0;padding-left:12px;border-left:2px solid var(--line-2);color:var(--muted)}.md h1,.md h2,.md h3,.md h4{font-size:1em;font-weight:650;letter-spacing:-.01em;margin:18px 0 2px;color:var(--fg)}.md h1+p,.md h2+p,.md h3+p,.md h4+p,.md h1+ul,.md h2+ul,.md h3+ul,.md h4+ul{margin-top:4px}.settings{max-width:780px;margin:0 auto;padding:24px 40px 64px;font-family:var(--prose);font-size:13px;line-height:1.6;color:var(--fg-2)}.settings .back{display:inline-block;font-family:var(--mono);font-size:11.5px;text-decoration:none;margin-bottom:16px}.settings h1{font-family:var(--mono);font-size:15px;margin:32px 0 8px}.settings h2{font-family:var(--mono);font-size:13px;margin:28px 0 8px}.settings code{font-family:var(--mono);font-size:.92em;background:#f0f2f5;padding:1px 5px;border-radius:3px}.inbox-toggle{display:block;margin:8px 0}.trust-add{display:flex;gap:8px;margin:20px 0 12px}.trust-add input{flex:1;font:inherit;padding:7px 10px;color:var(--fg);border:1px solid var(--line-2);border-radius:4px;background:var(--surface)}.trust-table{border-collapse:collapse;width:100%}.trust-table td{padding:6px 10px 6px 0;border-bottom:1px solid var(--line);vertical-align:baseline}.trust-actions{text-align:right}.trust-deny code{text-decoration:line-through}.tone-approve{color:var(--green)}.tone-comment{color:var(--amber)}.tone-changes{color:var(--red)}.tone-running,.tone-sent,.tone-none{color:var(--muted);font-weight:400}.tone-failed{color:var(--red)}.tone-awaiting{color:var(--accent)}.tone-bg-approve{background:var(--green)}.tone-bg-comment{background:var(--amber)}.tone-bg-changes{background:var(--red)}.tone-border-approve{border-left:2px solid var(--green)}.tone-border-comment{border-left:2px solid var(--amber)}.tone-border-changes{border-left:2px solid var(--red)}.tone-border-none{border-left:2px solid var(--line-2)}.chip{font-size:11.5px;font-weight:500;padding:3px 9px;border-radius:3px;border:1px solid currentColor;background:#14171a08;white-space:nowrap}.chip-btn{display:inline-flex;align-items:center;gap:6px;font-family:inherit}.chip-btn:hover{background:#14171a12}.tag{font-size:10px;letter-spacing:.06em;padding:1px 6px;border-radius:3px;color:var(--muted);background:#f0f1f3;white-space:nowrap}.tag-yours{color:var(--accent);background:#3d4fd617}.tag-kept{color:var(--green);background:#1a7f5a1a}.tag-drift{color:var(--amber);background:#a06a001a}.sev-counts{font-size:12px;color:var(--muted);margin-bottom:6px}.verdict-hint{display:flex;align-items:center;gap:10px;flex-wrap:wrap;background:#a06a0012;border:1px solid rgba(160,106,0,.3);color:#57430a;padding:10px 12px;border-radius:4px}
10
+ */.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-variable,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id{color:#005cc5}.hljs-regexp,.hljs-string,.hljs-meta .hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-comment,.hljs-code,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-weight:700}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0}:root{--bg: #fcfcfd;--surface: #ffffff;--fg: #14171a;--fg-2: #3a3f45;--fg-3: #4a5058;--muted: #6b7178;--faint: #9aa0a8;--fainter: #b6bbc1;--line: #e6e8eb;--line-2: #dcdfe4;--line-3: #eef0f2;--accent: #3d4fd6;--accent-dark: #2c3aa8;--accent-wash: #f4f5ff;--green: #1a7f5a;--amber: #a06a00;--red: #c2314a;--mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;--prose: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Helvetica, sans-serif;--measure: 1560px}*{box-sizing:border-box}body{margin:0;font-family:var(--mono);font-size:12px;color:var(--fg);background:var(--bg)}a{color:var(--accent)}a:hover{color:var(--accent-dark)}h1,h2,h3{font-size:13px;font-weight:700;margin:0}.app{padding-bottom:48px}.wrap{max-width:var(--measure);margin:0 auto;padding:0 40px}.bar-inner{max-width:var(--measure);margin:0 auto;padding:0 40px;display:flex;align-items:center;gap:14px}.grow{flex-grow:1}.pad{padding:24px 40px}.muted,.faint{color:var(--faint)}.warn{color:var(--amber)}.danger{color:var(--red)}.add{color:var(--green)}.del{color:var(--red)}.icon{flex-shrink:0}.lab{font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--faint)}.key{font-size:10px;border:1px solid var(--line-2);border-bottom-width:2px;border-radius:3px;padding:1px 5px;color:var(--muted);background:var(--surface)}.hint{display:inline-flex;gap:5px;align-items:center;font-size:10.5px;color:var(--faint);white-space:nowrap}.prose{font-family:var(--prose);font-size:13px;line-height:1.6;color:var(--fg-2);text-wrap:pretty;max-width:720px}.prose.lead{font-size:13.5px;line-height:1.62;color:var(--fg)}.error{color:var(--red);background:#c2314a0f;border:1px solid rgba(194,49,74,.3);padding:8px 12px;border-radius:4px;margin:10px 0}.empty{text-align:center;color:var(--faint);margin:64px auto;flex-direction:column}.empty pre{display:inline-block;background:var(--line-3);padding:8px 16px;border-radius:4px}button{font:inherit;cursor:pointer}.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;font-size:11px;color:var(--fg-3);border:1px solid var(--line-2);border-radius:4px;padding:6px 10px;background:var(--surface);white-space:nowrap;text-decoration:none}.btn:hover:not(:disabled){color:var(--fg);border-color:var(--fainter)}.btn:disabled{opacity:.5;cursor:default}.btn-sm{font-size:10.5px;padding:3px 8px;gap:5px}.btn-accent{color:var(--accent);border-color:#3d4fd659}.btn-keep-on{color:#fff;background:var(--green);border-color:var(--green)}.btn-drop-on{color:var(--red);border-color:#c2314a66}.btn-dark{color:#fff;background:var(--fg);border-color:var(--fg);font-size:11.5px;padding:7px 14px}.btn-dark:disabled{color:var(--faint);background:#f0f1f3;border-color:var(--line-2)}.btn-dashed{display:inline-flex;align-items:center;gap:5px;font-size:10.5px;color:var(--muted);border:1px dashed var(--line-2);border-radius:4px;padding:5px 10px;background:transparent;margin-top:10px}.btn-primary{display:flex;align-items:center;justify-content:center;gap:8px;height:40px;padding:0 14px;font-size:12px;font-weight:500;color:#fff;background:var(--accent);border:1px solid var(--accent);border-radius:4px}.btn-primary .key{border-color:#ffffff4d;color:#ffffffbf;background:transparent}.link{display:inline-flex;align-items:center;gap:5px;font:inherit;font-size:11px;color:var(--accent);background:none;border:none;padding:0;text-decoration:none}.link:hover:not(:disabled){color:var(--accent-dark)}.link:disabled{color:var(--faint);cursor:default}.topbar{border-bottom:1px solid var(--line);background:var(--surface)}.topbar .bar-inner{padding-top:14px;padding-bottom:14px}.brand{display:inline-flex;align-items:center;gap:7px;font-size:15px;font-weight:700;letter-spacing:-.02em;color:var(--fg);text-decoration:none}.brand .logo{color:var(--accent);flex-shrink:0}.tagline{font-size:11px;color:var(--faint)}.topbar-link{font-size:11px;color:var(--muted);text-decoration:none;border-left:1px solid var(--line);padding-left:14px;display:inline-flex;align-items:center}.topbar-link:hover{color:var(--fg)}.bell{background:none;border:0;border-left:1px solid var(--line);padding:0 0 0 14px;font-family:inherit;cursor:pointer}.bell-on,.bell-ask{color:var(--accent)}.bell-blocked{color:var(--fainter);cursor:help}.daemon-bar{border-bottom:1px solid var(--line);background:#f7f8fa;font-size:11px;color:var(--muted)}.daemon-bar .bar-inner{padding-top:8px;padding-bottom:8px;gap:18px;flex-wrap:wrap}.daemon-bar b{color:var(--fg);font-weight:400}.daemon-live{display:inline-flex;align-items:center;gap:6px;color:var(--fg)}.daemon-bar .warn{display:inline-flex;align-items:center;gap:6px}.dot{width:5px;height:5px;border-radius:999px;background:var(--green);display:inline-block}.dot-off{background:var(--fainter)}.tabs{display:flex;align-items:center;gap:2px;padding-top:14px}.tab{font-size:12px;padding:6px 12px;border:none;border-bottom:2px solid transparent;border-radius:4px 4px 0 0;background:none;color:var(--muted)}.tab:hover{color:var(--fg)}.tab-on{color:var(--fg);font-weight:500;border-bottom-color:var(--accent)}.tab-count{color:var(--fainter);margin-left:4px}.tab-on .tab-count{color:var(--accent)}.tab-split{width:1px;flex-shrink:0;align-self:stretch;margin:4px 10px 6px;background:var(--line-2)}.tab-filed{color:var(--faint)}.tab-filed:hover,.tab-filed.tab-on{color:var(--muted)}.tab-requests{color:var(--amber)}.tab-requests:hover,.tab-requests.tab-on{color:var(--amber);border-bottom-color:var(--amber)}.tab-requests .tab-count,.tab-requests.tab-on .tab-count{color:var(--amber)}.empty-tab{padding:22px 10px;font-size:12.5px;max-width:88ch}.empty-tab .btn{margin-top:10px}.table-head,.row{display:flex;gap:12px;align-items:center}.table-head{font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--faint);padding:12px 10px 7px;border-bottom:1px solid var(--line-2)}.row{font-size:12px;padding:8px 10px;cursor:pointer;border-bottom:1px solid var(--line-3);border-left:2px solid transparent}.row:hover{background:#f7f8fa}.row-on{background:var(--accent-wash);border-left-color:var(--accent)}.row-on .col-caret{color:var(--accent)}.row-on .col-title{font-weight:500}.tag-done{flex-shrink:0;color:var(--muted);background:var(--line-3)}.tag-awaiting{flex-shrink:0;color:var(--amber);background:#a06a001a}.pull{display:flex;align-items:center;gap:6px;position:relative;color:var(--muted)}.tabs .pull{margin-right:16px}.empty .pull{justify-content:center;margin-top:10px}.pull:hover,.pull:focus-within{color:var(--fg-3)}.pull-input{width:200px;max-width:100%;font:inherit;font-size:12px;padding:2px 0;color:var(--fg);background:transparent;border:none;border-bottom:1px solid transparent;transition:width .12s ease,border-color .12s ease}.pull-input:hover::placeholder{color:var(--fg)}.pull-input:focus{outline:none;width:320px;border-bottom-color:var(--accent)}.pull-input::placeholder{color:var(--muted);transition:color .12s ease}.btn-small{padding:2px 8px;font-size:10px}.pull-error{position:absolute;top:100%;right:0;z-index:5;margin-top:4px;max-width:360px;padding:5px 8px;font-size:11px;line-height:1.4;color:var(--red);background:var(--bg);border:1px solid var(--line-2);border-radius:4px;box-shadow:0 2px 8px #14171a14}.tag-draft{flex-shrink:0;color:var(--muted);background:var(--line-3)}.col-caret{width:12px;flex-shrink:0;color:transparent}.col-slug{width:184px;flex-shrink:0;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.col-title{flex-grow:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.col-author{width:104px;flex-shrink:0;color:var(--faint);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.col-verdict{width:136px;flex-shrink:0;text-align:right;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.col-comments{width:26px;flex-shrink:0;text-align:right;color:var(--muted)}.col-size{width:104px;flex-shrink:0;text-align:right;color:var(--muted)}.col-age{width:44px;flex-shrink:0;text-align:right;color:var(--faint)}.table-head .col-verdict,.table-head .col-slug,.table-head .col-author{color:var(--faint);font-weight:400}.cursor-strip{display:flex;gap:18px;align-items:flex-start;border:1px solid var(--line-2);border-top:none;border-radius:0 0 4px 4px;background:var(--surface);padding:14px 16px}.cursor-strip-body{flex-grow:1;min-width:0}.strip-prose{--strip-cap: 196px;--strip-fade: 46px;margin:6px 0 0;max-width:700px;max-height:var(--strip-cap);overflow:hidden}.strip-prose-clipped{-webkit-mask-image:linear-gradient(#000 calc(var(--strip-cap) - var(--strip-fade)),transparent var(--strip-cap));mask-image:linear-gradient(#000 calc(var(--strip-cap) - var(--strip-fade)),transparent var(--strip-cap))}.cursor-strip-body .md p{margin:5px 0}.cursor-strip-body .md ul,.cursor-strip-body .md ol{margin:5px 0;padding-left:17px}.cursor-strip-body .md li{margin:2px 0}.cursor-strip-body .md li::marker{color:var(--faint)}.cursor-strip-body .md strong{font-weight:600;color:var(--fg)}.cursor-strip-meta{font-size:10.5px;color:var(--faint);margin-top:8px}.cursor-strip-actions{display:flex;flex-direction:column;gap:6px;width:176px;flex-shrink:0}.cursor-strip-row{display:flex;gap:6px}.cursor-strip-row .btn{flex-grow:1;padding:7px 0}.review-top{position:sticky;top:0;z-index:20;background:var(--surface)}.crumb{border-bottom:1px solid var(--line);background:var(--surface);font-size:11.5px;color:var(--muted)}.crumb .bar-inner{padding-top:12px;padding-bottom:12px;gap:12px}.crumb a{text-decoration:none}.crumb-sep{color:var(--line-2)}.crumb-slug{color:var(--fg)}.crumb-next{font-size:11px;max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.walk{display:inline-flex;gap:2px}.walk-btn{display:inline-flex;align-items:center;justify-content:center;padding:4px 6px;border:1px solid var(--line-2);border-radius:3px;background:var(--surface);color:var(--fg-3)}.walk-btn:disabled{color:#d4d7db;border-color:var(--line-3);cursor:default}.review-head{border-bottom:1px solid var(--line);background:var(--surface)}.review-head .wrap{padding-top:20px;padding-bottom:16px}.review-head-top,.review-head-meta{display:flex;align-items:center;gap:14px}.review-head-top{margin-bottom:8px}.review-title{font-size:19px;font-weight:700;letter-spacing:-.025em;color:var(--fg);text-decoration:none}.review-title:hover{color:var(--accent)}.review-head-meta{font-size:11px;color:var(--muted);gap:10px;flex-wrap:wrap}.verdict-pick{display:inline-flex;align-items:center;gap:5px;font-size:10.5px;padding:4px 9px;border-radius:4px;border:1px solid transparent;background:none;color:var(--faint);white-space:nowrap}.verdict-pick:hover{color:var(--fg-3)}.verdict-pick-on{background:#f0f1f3;border-color:var(--line-2);font-weight:500}.review-body{display:grid;align-items:start;grid-template-columns:236px minmax(0,1fr);grid-template-areas:"rail main" "rail cockpit"}.rail{grid-area:rail}.main{grid-area:main}.cockpit{grid-area:cockpit;min-width:0;padding-bottom:18px}.rail{padding:22px 20px 22px 0;position:sticky;top:var(--top-h, 0px);z-index:5;max-height:calc(100vh - var(--top-h, 0px));overflow-y:auto}@media(min-width:1600px){.review{--measure: 2100px}.review-body{grid-template-columns:236px minmax(0,1fr) clamp(420px,34%,680px);grid-template-areas:"rail main cockpit"}.cockpit{position:sticky;top:var(--top-h, 0px);height:calc(100vh - var(--top-h, 0px));padding:22px 0 18px 22px;display:flex;flex-direction:column;justify-content:space-between;gap:10px}.cockpit .chat-panel{flex:0 1 auto;display:flex;flex-direction:column;min-height:120px;margin:0}.cockpit .chat-panel .card-body{display:flex;flex-direction:column;min-height:0}.cockpit .chat-log{flex:1 1 auto;min-height:0;overflow-y:auto}.cockpit .verdict-bar{margin-top:0}.cockpit-decide{flex:0 0 auto;max-height:65%;overflow-y:auto}}.chapter,.comment,.card-summary,.card-why,.chat-panel,.send-panel,.sent-strip,.verdict-bar{scroll-margin-top:calc(var(--top-h, 0px) + 12px)}.rail-lab{display:block;margin-top:22px}.rail-jumps{display:flex;flex-direction:column;align-items:flex-start;gap:7px}.rail .rail-jumps .lab{margin-bottom:0}.rail-lab-btn{display:block;width:100%;text-align:left;font:inherit;padding:0;background:none;border:none}.rail-lab-btn:hover{color:var(--fg)}.rail .lab{margin-bottom:10px}.rail-item{display:flex;align-items:center;gap:6px;width:100%;text-align:left;font:inherit;font-size:11.5px;line-height:1.5;padding:5px 0 5px 9px;margin-left:-11px;background:none;border:none;border-left:2px solid transparent;color:var(--muted)}.rail-item:hover{color:var(--fg)}.rail-item-on{border-left-color:var(--accent);color:var(--fg);font-weight:500}.rail-group{margin-bottom:2px}.rail-count{font-size:10.5px;color:var(--fainter)}.rail-comment{display:flex;align-items:center;gap:6px;width:100%;font:inherit;font-size:11px;text-align:left;padding:3px 0 3px 20px;margin-left:-11px;background:none;border:none;border-left:2px solid transparent;color:var(--muted)}.rail-comment:hover{color:var(--fg)}.rail-comment-loc{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rail-comment-dropped{opacity:.55}.rail-comment-dropped .rail-comment-loc{text-decoration:line-through}.rail-dot{font-size:8px}.comment-flash{animation:comment-flash 1.8s ease-out}@keyframes comment-flash{0%,60%{background:var(--accent-wash);box-shadow:inset 0 0 0 2px #3d4fd673}}.rail-facts{font-size:11.5px;line-height:1.85;color:var(--muted)}.rail-facts b{color:var(--fg);font-weight:400}.main{min-width:0;padding:22px 0 0}.card{border:1px solid var(--line);border-radius:4px;background:var(--surface);margin-bottom:10px}.card-head{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;padding:11px 16px;border-bottom:1px solid var(--line-3)}.card-head .faint{font-size:11px}.card-body{padding:14px 16px}.card-body .lab{display:block;margin-bottom:7px}.card-body .prose{margin:0}.card-body .btn{margin-top:12px}.card-why{margin-bottom:8px}.chapter{border:1px solid var(--line);border-radius:4px;background:var(--surface);margin-bottom:10px;overflow:hidden}.chapter-head{display:flex;align-items:center;gap:10px;padding:11px 16px;cursor:pointer;-webkit-user-select:none;user-select:none}.chapter-body{padding:14px 16px 16px;border-top:1px solid var(--line-3)}.chapter-meta{font-size:11px;font-weight:400}.chapter-explanation{margin:0 0 14px}.comment{border:1px solid var(--line);border-radius:4px;background:#fbfbfc;padding:12px 14px;margin:8px 0}.comment-dropped{opacity:.5}.comment-dropped .comment-body{text-decoration:line-through}.comment-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:6px}.comment-loc{font-size:10.5px;color:var(--faint)}.comment-body{font-size:13px;line-height:1.58;color:var(--fg);max-width:640px}.comment-edit,.add-comment textarea,.chat-input{width:100%;font-family:var(--mono);font-size:12.5px;line-height:1.55;color:var(--fg);padding:10px 12px;border:1px solid var(--line-2);border-radius:4px;background:var(--surface);resize:vertical}.add-comment{border:1px dashed var(--line-2);border-radius:4px;padding:10px 12px;margin:10px 0;display:flex;flex-direction:column;gap:8px}.add-comment-row{display:flex;gap:8px;align-items:center}.add-comment select,.add-comment input{font:inherit;font-size:11.5px;padding:5px 7px;border:1px solid var(--line-2);border-radius:4px;background:var(--surface)}.inline-comment-row td{padding:0;background:#fbfbfc;border-top:1px solid var(--line);border-bottom:1px solid var(--line);white-space:normal}.inline-comment-row .comment{margin:0;border:none;border-radius:0;padding-left:44px;max-width:none}.line-composer-row td{padding:0;background:var(--accent-wash);border-top:1px solid var(--line);border-bottom:1px solid var(--line);white-space:normal}.line-composer{display:flex;flex-direction:column;gap:8px;padding:10px 12px 10px 44px;max-width:760px;font-family:var(--mono)}.line-composer-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.line-composer-actions{display:flex;align-items:center;gap:8px}.diff{margin-top:8px;font-size:11.5px}.diff .line-add{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;padding:0;border:0;background:none;cursor:pointer;opacity:0}.diff tr:hover .line-add{opacity:1}.diff .line-add:before{content:"+";position:absolute;left:1px;top:50%;transform:translateY(-50%);width:13px;height:13px;border-radius:3px;background:var(--accent);color:#fff;font-family:var(--mono);font-size:11px;font-weight:700;line-height:12px;text-align:center;direction:ltr}.diff .d2h-file-header,.diff .d2h-file-name{font-family:var(--mono);font-size:11px}.diff .d2h-code-line{white-space:normal;width:calc(100% - 9em);padding-right:1em}.diff .d2h-code-line-ctn{display:inline;width:auto;white-space:pre-wrap;overflow-wrap:anywhere}.diff .d2h-code-line ins,.diff .d2h-code-line del{display:inline}.diff .hljs{display:inline;padding:0;background:transparent;color:inherit}.freshness{background:#a06a0012;border:1px solid rgba(160,106,0,.3);color:#57430a;padding:10px 12px;border-radius:4px;margin-bottom:10px;line-height:1.55;font-size:11.5px}.freshness code{background:#0000000f;padding:1px 4px;border-radius:3px}.freshness-running{background:#3d4fd60f;border-color:#3d4fd64d;color:#26307a}.freshness .btn{margin-left:8px}.chat-panel{margin:18px 0 10px}.chat-log{display:flex;flex-direction:column;gap:14px;margin-bottom:14px}.chat-turn{border-left:2px solid var(--line-2);padding:2px 0 2px 12px}.chat-turn-user{border-left-color:var(--accent)}.chat-turn-pending{border-left-style:dashed}.chat-turn-who{font-size:10.5px;color:var(--faint);margin-bottom:4px}.chat-turn-body{max-width:700px;color:var(--fg)}.chat-revision{font-size:10.5px;color:var(--green);margin-top:6px}.chat-refusal{font-size:10.5px;color:var(--amber);margin-top:6px}.chat-waiting{font-size:11.5px;color:var(--muted);margin-top:6px}.chat-progress{list-style:none;margin:0;padding:0}.chat-progress li{opacity:.55;padding:1px 0}.chat-progress li.now{opacity:1}.chat-progress li.now:after{content:"โ€ฆ"}.chat-failed{font-size:11.5px;color:var(--red);margin-top:6px}.chat-refs{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-bottom:8px}.chat-ref{font-size:10.5px;font-family:var(--mono);padding:3px 9px;border:1px solid var(--line-2);border-radius:999px;color:var(--muted);background:transparent}.chat-refs-pending .chat-ref{cursor:pointer;color:var(--accent);border-color:#3d4fd659}.chat-actions{display:flex;align-items:center;gap:12px;margin-top:10px}.send-panel{border:1px solid var(--line-2);border-radius:4px;background:#f7f8fa;padding:16px 18px}.verdict-bar+.send-panel{border-radius:0 0 4px 4px}.send-top{display:flex;align-items:baseline;gap:12px;margin-bottom:14px}.send-top .faint{font-size:11px}.send-top .link{text-decoration:underline;text-underline-offset:2px}.send-action{display:flex;align-items:center;gap:16px;flex-wrap:wrap}.send-btn{display:inline-flex;align-items:center;gap:10px;height:44px;padding:0 20px;font-size:13px;font-weight:500;color:#fff;border:none;border-radius:4px;box-shadow:0 1px 2px #14171a1f}.send-btn:disabled{opacity:.6;cursor:default}.send-btn .key{border-color:#ffffff4d;color:#fffc;background:transparent}.send-as{position:relative}.send-as-toggle{font:inherit;font-size:11.5px;color:var(--fg-3);background:none;border:none;padding:0}.send-as-toggle b{color:var(--fg);font-weight:500;border-bottom:1px dotted var(--fainter)}.send-as-why{font-size:10.5px;color:var(--faint);margin-top:3px}.menu{position:absolute;top:34px;left:0;z-index:10;background:var(--surface);border:1px solid var(--line-2);border-radius:4px;box-shadow:0 6px 18px #14171a17;padding:4px;min-width:190px}.menu-item{display:flex;align-items:center;gap:9px;width:100%;font:inherit;font-size:11.5px;text-align:left;padding:7px 9px;border:none;border-radius:3px;background:none;color:var(--fg-3)}.menu-item:hover{background:#f7f8fa}.menu-item-on{background:#f4f5f7;color:var(--fg)}.menu-check{font-size:10px;color:var(--green)}.verdict-bar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;border:1px solid var(--line-2);border-bottom:none;border-radius:4px 4px 0 0;background:var(--surface);padding:10px 18px;margin-top:18px}.verdict-bar .lab{margin-right:4px}.verdict-bar .faint{font-size:10.5px}.send-footer{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:16px;padding-top:14px;border-top:1px solid var(--line-2);font-size:10.5px}.body-preview{margin-top:14px;padding:14px 16px;background:var(--surface);border:1px solid var(--line);border-radius:4px;font-family:var(--prose);font-size:12.5px;line-height:1.62;color:var(--fg-2);white-space:pre-wrap;max-width:760px;max-height:420px;overflow:auto}.sent-strip{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;border:1px solid rgba(26,127,90,.4);background:#1a7f5a12;border-radius:4px;padding:14px 16px;font-size:11.5px}.sent-strip strong{color:var(--green);font-size:13px;white-space:nowrap}.sent-strip a{display:inline-flex;align-items:center;gap:5px;white-space:nowrap;text-decoration:none}.post-actions{display:flex;align-items:center;gap:12px;margin-top:12px;font-size:10.5px}.post-actions .link{font-size:10.5px;color:var(--muted)}.post-actions .link:hover{color:var(--fg)}.md>:first-child{margin-top:0}.md>:last-child{margin-bottom:0}.md p{margin:8px 0}.md code{font-family:var(--mono);font-size:.9em;background:#f0f2f5;padding:1px 5px;border-radius:3px}.md pre{background:#f7f8fa;border:1px solid var(--line);border-radius:4px;padding:10px 12px;overflow-x:auto}.md pre code{background:none;padding:0;font-size:11.5px}.md ul,.md ol{margin:8px 0;padding-left:24px}.md blockquote{margin:8px 0;padding-left:12px;border-left:2px solid var(--line-2);color:var(--muted)}.md h1,.md h2,.md h3,.md h4{font-size:1em;font-weight:650;letter-spacing:-.01em;margin:18px 0 2px;color:var(--fg)}.md h1+p,.md h2+p,.md h3+p,.md h4+p,.md h1+ul,.md h2+ul,.md h3+ul,.md h4+ul{margin-top:4px}.settings{max-width:780px;margin:0 auto;padding:24px 40px 64px;font-family:var(--prose);font-size:13px;line-height:1.6;color:var(--fg-2)}.settings .back{display:inline-block;font-family:var(--mono);font-size:11.5px;text-decoration:none;margin-bottom:16px}.settings h1{font-family:var(--mono);font-size:15px;margin:32px 0 8px}.settings h2{font-family:var(--mono);font-size:13px;margin:28px 0 8px}.settings code{font-family:var(--mono);font-size:.92em;background:#f0f2f5;padding:1px 5px;border-radius:3px}.inbox-toggle{display:block;margin:8px 0}.trust-add{display:flex;gap:8px;margin:20px 0 12px}.trust-add input{flex:1;font:inherit;padding:7px 10px;color:var(--fg);border:1px solid var(--line-2);border-radius:4px;background:var(--surface)}.trust-table{border-collapse:collapse;width:100%}.trust-table td{padding:6px 10px 6px 0;border-bottom:1px solid var(--line);vertical-align:baseline}.trust-actions{text-align:right}.trust-deny code{text-decoration:line-through}.tone-approve{color:var(--green)}.tone-comment{color:var(--amber)}.tone-changes{color:var(--red)}.tone-running,.tone-sent,.tone-none{color:var(--muted);font-weight:400}.tone-failed{color:var(--red)}.tone-awaiting{color:var(--accent)}.tone-bg-approve{background:var(--green)}.tone-bg-comment{background:var(--amber)}.tone-bg-changes{background:var(--red)}.tone-border-approve{border-left:2px solid var(--green)}.tone-border-comment{border-left:2px solid var(--amber)}.tone-border-changes{border-left:2px solid var(--red)}.tone-border-none{border-left:2px solid var(--line-2)}.chip{font-size:11.5px;font-weight:500;padding:3px 9px;border-radius:3px;border:1px solid currentColor;background:#14171a08;white-space:nowrap}.chip-btn{display:inline-flex;align-items:center;gap:6px;font-family:inherit}.chip-btn:hover{background:#14171a12}.tag{font-size:10px;letter-spacing:.06em;padding:1px 6px;border-radius:3px;color:var(--muted);background:#f0f1f3;white-space:nowrap}.tag-yours{color:var(--accent);background:#3d4fd617}.tag-kept{color:var(--green);background:#1a7f5a1a}.tag-drift{color:var(--amber);background:#a06a001a}.sev-counts{font-size:12px;color:var(--muted);margin-bottom:6px}.verdict-hint{display:flex;align-items:center;gap:10px;flex-wrap:wrap;background:#a06a0012;border:1px solid rgba(160,106,0,.3);color:#57430a;padding:10px 12px;border-radius:4px}