@fullstackhouse/cerber 0.19.0 → 0.21.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
@@ -60,6 +60,20 @@ inbox — cerber never tells you nothing awaits you while its own poll says
60
60
  otherwise. It is a report, not a nag: what you decided here stands, and the
61
61
  mark clears itself the moment GitHub hears from you.
62
62
 
63
+ ### Except when you reviewed it on GitHub
64
+
65
+ There is one thing you never have to settle by hand. Review a PR the ordinary
66
+ way — GitHub's own review button, cerber not involved — and the review request
67
+ clears, but nothing here hears about it: the draft cerber wrote for that PR
68
+ goes on sitting in the inbox as work still waiting on you, days after the PR
69
+ went back to its author. So the poll asks the other question too. When GitHub
70
+ has stopped requesting your review *and* already holds one of yours, the draft
71
+ is filed under settled, tagged `reviewed on GitHub` with the date it was
72
+ submitted. Nothing else happens to it — it was never sent, and it is still
73
+ yours to open, re-review or send. The one draft this leaves alone is one you
74
+ asked for *after* that review: a second opinion you went and requested is not
75
+ a row the queue forgot to close.
76
+
63
77
  ### Whose move is it
64
78
 
65
79
  An open request doesn't mean the ball is yours, though. You may have argued the
package/dist/cli/index.js CHANGED
@@ -321,7 +321,7 @@ program
321
321
  .option("--no-source", "review the diff alone, with no local checkout of the PR head (applies to daemon runs and cockpit re-reviews)")
322
322
  .option("--no-trust", "ignore the configured trust rules: every review here stays read-only, even for repos you trust")
323
323
  .option("--auto-send", "ACTUALLY auto-send APPROVE verdicts at/above --auto-send-threshold (default: shadow mode, which only logs what would be sent)")
324
- .option("--auto-send-threshold <pct>", "confidence needed to auto-send", "90")
324
+ .option("--auto-send-threshold <pct>", "how sure the review must be of itself to auto-send", "90")
325
325
  .option("--insecure", "allow binding a non-localhost host without a token (NOT recommended)")
326
326
  .action(async (opts) => {
327
327
  const token = opts.token ?? process.env.CERBER_TOKEN;
@@ -35,7 +35,13 @@ export const ChapterSchema = z.object({
35
35
  });
36
36
  export const VerdictSchema = z.object({
37
37
  recommendation: z.enum(["approve", "comment", "request_changes"]),
38
- /** 0-100. How confident the AI is that its recommendation is right. */
38
+ /**
39
+ * 0-100. How sure the AI is that its own review is right — the findings real,
40
+ * the grades correct, nothing worth blocking missed. Not a claim about
41
+ * whether the PR should merge: the verdict says that, and it follows from the
42
+ * blockers. It is the dial the auto-send threshold turns, and is otherwise
43
+ * shown beside the findings it is about.
44
+ */
39
45
  confidence: z.number().min(0).max(100),
40
46
  reasoning: z.string(),
41
47
  });
@@ -83,6 +89,14 @@ export const RunInfoSchema = z.object({
83
89
  * reviewer answering a question still holds everything it read.
84
90
  */
85
91
  sessionId: z.string().nullable().default(null),
92
+ /**
93
+ * Who asked for this run: the inbox poll, or a person — the cockpit's review
94
+ * button, a pasted URL, `cerber review`. Null on runs recorded before this
95
+ * was kept. It is what tells a draft the queue produced on its own from one
96
+ * you deliberately asked for, which is the difference between a row that can
97
+ * be filed away automatically and one that must not be.
98
+ */
99
+ trigger: z.enum(["daemon", "user"]).nullable().default(null),
86
100
  });
87
101
  export const SentInfoSchema = z.object({
88
102
  at: z.string(),
@@ -91,6 +105,23 @@ export const SentInfoSchema = z.object({
91
105
  /** True when the daemon auto-sent this review (opt-in --auto-send). */
92
106
  auto: z.boolean().default(false),
93
107
  });
108
+ /**
109
+ * Why a draft was filed away without ever being sent.
110
+ *
111
+ * The only cause today is the one the queue could not see before: you reviewed
112
+ * the PR through GitHub itself, so cerber's draft is a second opinion on a
113
+ * conversation you already had. It is a record, not a decision to defend — the
114
+ * review is untouched, still openable and still sendable.
115
+ */
116
+ export const FiledInfoSchema = z.object({
117
+ at: z.string(),
118
+ /** The review of yours GitHub had, which is what settled this one. */
119
+ review: z.object({
120
+ at: z.string(),
121
+ state: z.enum(["APPROVED", "CHANGES_REQUESTED", "COMMENTED"]),
122
+ url: z.string().nullable().default(null),
123
+ }),
124
+ });
94
125
  /** Result of pulling a review forward onto a newer head commit. */
95
126
  export const RefreshInfoSchema = z.object({
96
127
  at: z.string(),
@@ -219,6 +250,8 @@ export const ArtifactSchema = z.object({
219
250
  run: RunInfoSchema.nullable().default(null),
220
251
  /** Set once the review was sent to GitHub (explicitly, or via opt-in auto-send). */
221
252
  sent: SentInfoSchema.nullable().default(null),
253
+ /** Set when cerber filed this draft away itself, and why. Never on a sent one. */
254
+ filed: FiledInfoSchema.nullable().default(null),
222
255
  /** Last time this review was pulled forward onto a newer head commit. */
223
256
  refresh: RefreshInfoSchema.nullable().default(null),
224
257
  calibration: CalibrationSchema.nullable().default(null),
@@ -9,7 +9,15 @@ export function toMarkdown(artifact) {
9
9
  lines.push(`> ${pr.author} · \`${pr.headRefName}\` → \`${pr.baseRefName}\` · ${pr.changedFiles} files +${pr.additions} -${pr.deletions}`);
10
10
  lines.push("");
11
11
  if (verdict) {
12
- lines.push(`**Verdict: ${verdict.recommendation.replace("_", " ")}** (confidence ${verdict.confidence}%)`);
12
+ // Two different claims side by side: the blocker count the verdict follows
13
+ // from, and how sure the review is that its own findings are right.
14
+ const graded = artifact.comments.some((c) => c.severity != null);
15
+ const blockers = artifact.comments.filter((c) => c.status !== "dropped" && c.severity === "blocker").length;
16
+ const basis = !graded
17
+ ? []
18
+ : [blockers === 0 ? "no blockers" : `${blockers} blocker${blockers === 1 ? "" : "s"}`];
19
+ basis.push(`${verdict.confidence}% sure of the findings`);
20
+ lines.push(`**Verdict: ${verdict.recommendation.replace("_", " ")}** (${basis.join(" · ")})`);
13
21
  lines.push("");
14
22
  lines.push(verdict.reasoning);
15
23
  lines.push("");
package/dist/core/gh.js CHANGED
@@ -158,6 +158,38 @@ export function classifyReply(comments, login) {
158
158
  // answer to it, so only strictly-later comments hand the move back.
159
159
  return human.some((c) => c.author !== login && c.at > lastMine) ? "them" : "you";
160
160
  }
161
+ /**
162
+ * The last review of your own that still stands, or null.
163
+ *
164
+ * PENDING is a draft on GitHub's side and has no `submitted_at`, so it is not
165
+ * a review anyone has seen. DISMISSED is one a maintainer explicitly struck
166
+ * off; taking it as "you have had your say" would file work away on the
167
+ * strength of a review GitHub no longer counts.
168
+ */
169
+ const STANDS = new Set(["APPROVED", "CHANGES_REQUESTED", "COMMENTED"]);
170
+ export function latestOwnReview(rows, login) {
171
+ const mine = rows.filter((r) => r.author === login && r.at != null && STANDS.has(r.state));
172
+ if (mine.length === 0)
173
+ return null;
174
+ const last = mine.reduce((a, b) => (b.at > a.at ? b : a));
175
+ return { at: last.at, state: last.state, url: last.url };
176
+ }
177
+ /** Ask GitHub whether you have reviewed this PR yourself. */
178
+ export async function fetchOwnReview(ref, login) {
179
+ const out = await gh([
180
+ "api",
181
+ "--paginate",
182
+ `repos/${ref.owner}/${ref.repo}/pulls/${ref.number}/reviews`,
183
+ "--jq",
184
+ ".[] | {author: .user.login, at: .submitted_at, state: .state, url: .html_url}",
185
+ ]);
186
+ // --jq streams one object per line rather than a JSON array.
187
+ const rows = out
188
+ .split("\n")
189
+ .filter((line) => line.trim() !== "")
190
+ .map((line) => JSON.parse(line));
191
+ return latestOwnReview(rows, login);
192
+ }
161
193
  /** The login `@me` resolves to. Asked once — it cannot change mid-process. */
162
194
  let cachedLogin = null;
163
195
  export function currentLogin() {
@@ -28,9 +28,13 @@ export function buildReviewPrompt(pr, diff, opts = {}) {
28
28
  ? "\n[... diff truncated — the rest of the change is in the checkout, read it there ...]"
29
29
  : "\n[... diff truncated ...]";
30
30
  const diffBody = truncated ? diff.slice(0, MAX_DIFF_CHARS) + cut : diff;
31
+ // Confidence is about the review, not about the merge. The verdict already
32
+ // answers "should this ship" and now follows mechanically from the blockers,
33
+ // so the only thing left for a number to say is how much to trust the
34
+ // findings it was derived from.
31
35
  const confidenceRule = opts.source
32
- ? `- "confidence" reflects how sure you are the recommendation is right. You can read the full source, so read it before hedging — only stay unsure about what the code genuinely cannot settle (intent, product decisions, runtime behaviour).`
33
- : `- "confidence" reflects how sure you are the recommendation is right given what you can see. You only see the diff, not the full codebase — factor that in.`;
36
+ ? `- "confidence" is how sure you are that THIS REVIEW is right — that each finding is real, each grade is the right one, and nothing worth blocking got past you. It is not how good an idea merging is: the verdict says that, and it follows from the blockers. A thorough read that found nothing is high confidence with an "approve"; a skim of an unfamiliar codebase is low confidence whatever the verdict. You can read the full source, so read it before hedging — only stay unsure about what the code genuinely cannot settle (intent, product decisions, runtime behaviour).`
37
+ : `- "confidence" is how sure you are that THIS REVIEW is right — that each finding is real, each grade is the right one, and nothing worth blocking got past you. It is not how good an idea merging is: the verdict says that, and it follows from the blockers. You only see the diff, not the full codebase, so anything you could not check holds this number down whatever the verdict.`;
34
38
  const prompt = `You are an expert senior code reviewer. Review the following GitHub pull request.
35
39
 
36
40
  Respond with ONLY a JSON object (no markdown fences, no prose before or after) matching exactly this shape:
@@ -98,8 +102,10 @@ Rules:
98
102
  - "minor" — should be fixed, but the author's call; you would approve and not chase it.
99
103
  - "nit" — taste: naming, wording, formatting.
100
104
  - null — not a finding at all: a question, a note, praise.
101
- - Grade severity as if the finding is true. If you do not believe it enough to grade it, do not write it — or ask it as a question with severity null. Never lower a grade to hedge doubt; doubt belongs in the verdict's "confidence", nowhere else.
102
- - The verdict must follow from the worst finding still standing: any blocker → "request_changes"; none → "approve" (or "comment" when you genuinely take no position). Name the deciding finding in "reasoning". Never request changes without a blocker in the list, and never approve past one.
105
+ - Grade severity as if the finding is true. If you do not believe it enough to grade it, do not write it — or ask it as a question with severity null. Never lower a grade to hedge doubt; doubt belongs in the verdict's "reasoning" — say what you could not settle and what would settle it — and in its "confidence", nowhere else.
106
+ - Once you have graded a single finding, the verdict follows from the worst one still standing and there is nothing in between: any blocker → "request_changes"; none → "approve". Name the deciding finding in "reasoning". Never request changes without a blocker in the list, and never approve past one.
107
+ - "Taking no position" is not a verdict. If something would stop you approving — a regression, a risk you can't rule out, a call you think the team should make first — that is a blocker: grade it and request changes. If it would not, approve and say what you'd still watch. Withholding approval over an ungraded finding is the same hedge as softening a grade, one field over.
108
+ - "comment" is for the one review the rule above does not cover: the review that graded nothing at all, because it found nothing to grade and only has questions. A review holding even one graded finding never uses it.
103
109
  ${confidenceRule}
104
110
  - Never invent files or lines not present in the diff.
105
111
 
@@ -123,9 +123,11 @@ async function runReview(ref, opts) {
123
123
  withSource: source !== null,
124
124
  trusted: trusted && source !== null,
125
125
  sessionId: null,
126
+ trigger: opts.trigger ?? "user",
126
127
  },
127
128
  sent: null,
128
129
  refresh: null,
130
+ filed: null,
129
131
  calibration: null,
130
132
  // The conversation is the user's writing, so a re-review keeps it — the
131
133
  // chat prompt replays the transcript, which is what makes it survive the
@@ -183,6 +185,7 @@ async function runReview(ref, opts) {
183
185
  // Only worth keeping when there is a checkout to resume into: a session
184
186
  // whose working directory was empty has nothing a chat turn could read.
185
187
  sessionId: source ? review.sessionId : null,
188
+ trigger: artifact.run.trigger,
186
189
  },
187
190
  };
188
191
  // A re-review regenerates the AI's comments, but the human's own comments
@@ -1,7 +1,7 @@
1
1
  import { SCHEMA_VERSION, artifactId, artifactKey } from "../core/artifact.js";
2
2
  import { evaluateAutoSend } from "../core/autosend.js";
3
3
  import { loadConfig } from "../core/config.js";
4
- import { classifyReply, currentLogin, fetchConversation, fetchPrInfo, searchAwaitingMe, submitReview, } from "../core/gh.js";
4
+ import { classifyReply, currentLogin, fetchConversation, fetchOwnReview, fetchPrInfo, searchAwaitingMe, submitReview, } from "../core/gh.js";
5
5
  import { buildReviewPayload, computeCalibration } from "../core/send.js";
6
6
  import { appendAutoSendLog, deleteArtifact, listArtifacts, loadArtifact, saveArtifact, updateArtifactByKey, } from "../core/state.js";
7
7
  import { ReviewInProgressError } from "../runner/inflight.js";
@@ -43,6 +43,7 @@ export function stubArtifact(ref) {
43
43
  run: null,
44
44
  sent: null,
45
45
  refresh: null,
46
+ filed: null,
46
47
  calibration: null,
47
48
  chat: [],
48
49
  preChat: null,
@@ -65,6 +66,39 @@ export function stubArtifact(ref) {
65
66
  export function isPureStub(artifact) {
66
67
  return artifact.status === "awaiting" && artifact.comments.length === 0 && artifact.run === null;
67
68
  }
69
+ /**
70
+ * Whether a review of yours on GitHub settles this draft.
71
+ *
72
+ * Two facts have to hold. GitHub must have stopped asking you — the caller has
73
+ * already established that, since this only runs on artifacts absent from the
74
+ * awaiting search — and you must have submitted a review of your own, which is
75
+ * the half cerber could not see before: a review clears the request whether it
76
+ * went through Send or through GitHub's own button, and only the first left a
77
+ * trace here. Together they mean the PR is back with its author, while the
78
+ * inbox went on listing the draft as work still waiting on you.
79
+ *
80
+ * The trigger test is what keeps this from undoing a deliberate act. A draft
81
+ * you asked for — pasted the URL in, pressed re-review — after you had already
82
+ * reviewed the PR is a second opinion you went and requested, so filing it
83
+ * away would answer a question you had just posed. A draft the poll wrote on
84
+ * its own carries no such intent: it is exactly the row this is here to close.
85
+ *
86
+ * A run recorded before the trigger was kept is treated as the poll's, which is
87
+ * the deliberate choice: the alternative leaves every draft written before this
88
+ * shipped stuck in the inbox forever, which is the bug. The cost of being wrong
89
+ * is one legacy draft filed once — and the re-review that un-files it records a
90
+ * trigger, so it cannot happen to the same row twice.
91
+ */
92
+ export function filedByOwnReview(artifact, review) {
93
+ if (artifact.status !== "ready" || artifact.sent || !review)
94
+ return false;
95
+ const run = artifact.run;
96
+ // Parsed, not compared as text. `startedAt` carries milliseconds and GitHub's
97
+ // `submitted_at` does not, and "…:00.500Z" sorts *before* "…:00Z" — so a run
98
+ // that began half a second after your review would read as older than it, and
99
+ // the second opinion this guard exists to protect would be filed away.
100
+ return !(run?.trigger === "user" && Date.parse(run.startedAt) >= Date.parse(review.at));
101
+ }
68
102
  /** How many open-PR artifacts to re-check against GitHub per poll, and how often each. */
69
103
  const STATE_REFRESH_CAP = 10;
70
104
  const STATE_REFRESH_MIN_MS = 30 * 60_000;
@@ -141,6 +175,40 @@ export function startDaemon(opts) {
141
175
  await saveArtifact(stubArtifact(ref));
142
176
  discovered++;
143
177
  }
178
+ // Asked once per pass, and only if a row actually reaches the check that
179
+ // needs it. Undefined is "not asked yet"; null is "gh could not say", which
180
+ // attributes nothing to you rather than guessing.
181
+ let login;
182
+ /**
183
+ * File a draft away once GitHub says you already reviewed the PR yourself.
184
+ *
185
+ * The queue lists what you have not dealt with locally, and a review
186
+ * submitted on github.com never reached it — so a PR you answered days ago
187
+ * sat in the inbox as if it still wanted you. This is the one read that
188
+ * closes that gap, on the same 30-minute-per-artifact leash as the state
189
+ * check it rides along with.
190
+ */
191
+ async function fileIfYouReviewedItYourself(artifact) {
192
+ // Only a finished, unsent draft can be filed — checked here too, so a row
193
+ // that could never qualify costs no GitHub call at all.
194
+ if (artifact.status !== "ready" || artifact.sent)
195
+ return;
196
+ if (login === undefined)
197
+ login = await currentLogin().catch(() => null);
198
+ if (login === null)
199
+ return;
200
+ const review = await fetchOwnReview(artifact.pr, login);
201
+ if (!review || !filedByOwnReview(artifact, review))
202
+ return;
203
+ const filed = { at: new Date().toISOString(), review };
204
+ const saved = await updateArtifactByKey(artifactKey(artifact.id), (a) =>
205
+ // Re-read from disk: a run or a send may have started in the seconds
206
+ // this read took, and neither wants filing out from under it.
207
+ filedByOwnReview(a, review) ? { ...a, status: "reviewed", filed } : a);
208
+ if (saved?.filed) {
209
+ log(`[${artifact.id}] you reviewed this on GitHub on ${review.at.slice(0, 10)} — filed as reviewed`);
210
+ }
211
+ }
144
212
  const awaitingIds = new Set(refs.map((r) => artifactId(r)));
145
213
  let refreshed = 0;
146
214
  for (const artifact of await listArtifacts()) {
@@ -193,6 +261,8 @@ export function startDaemon(opts) {
193
261
  }
194
262
  if (pr.state !== "OPEN")
195
263
  log(`[${artifact.id}] ${pr.state.toLowerCase()} — archived`);
264
+ else
265
+ await fileIfYouReviewedItYourself(artifact);
196
266
  }
197
267
  catch {
198
268
  // Same: book-keeping can wait for the next poll.
@@ -237,6 +307,7 @@ export function startDaemon(opts) {
237
307
  try {
238
308
  const result = await reviewPr(ref, {
239
309
  model: opts.model,
310
+ trigger: "daemon",
240
311
  withSource: opts.withSource,
241
312
  trust: opts.trust,
242
313
  onProgress: (m) => log(`[${label}] ${m}`),
@@ -154,11 +154,19 @@ export async function buildApp(opts) {
154
154
  verdict: a.verdict,
155
155
  commentCount: a.comments.filter((cm) => cm.status !== "dropped").length,
156
156
  driftedCount: a.comments.filter((cm) => cm.status !== "dropped" && cm.drifted).length,
157
+ // Only blockers block, so the count the verdict rests on is what the
158
+ // queue's strip names beside the comment count — the verdict cell
159
+ // itself is too narrow to spell it out.
160
+ blockerCount: a.comments.filter((cm) => cm.status !== "dropped" && cm.severity === "blocker")
161
+ .length,
162
+ gradedCount: a.comments.filter((cm) => cm.severity != null).length,
157
163
  costUsd: a.run?.costUsd ?? null,
158
164
  withSource: a.run?.withSource ?? null,
159
165
  trusted: a.run?.trusted ?? null,
160
166
  runError: a.run?.error ?? null,
161
167
  sent: a.sent ? { at: a.sent.at, event: a.sent.event, url: a.sent.url, auto: a.sent.auto ?? false } : null,
168
+ // Why a settled row is settled, when cerber settled it rather than you.
169
+ filed: a.filed,
162
170
  })));
163
171
  });
164
172
  // ---- Pull a PR in: review anything, not just what the inbox found ----
@@ -242,9 +250,11 @@ export async function buildApp(opts) {
242
250
  withSource: withSource !== false,
243
251
  trusted: false,
244
252
  sessionId: null,
253
+ trigger: "user",
245
254
  },
246
255
  sent: null,
247
256
  refresh: null,
257
+ filed: null,
248
258
  calibration: null,
249
259
  chat: [],
250
260
  preChat: null,
@@ -418,6 +428,7 @@ export async function buildApp(opts) {
418
428
  withSource: withSource !== false,
419
429
  trusted: false,
420
430
  sessionId: null,
431
+ trigger: "user",
421
432
  },
422
433
  }));
423
434
  // Minutes-long: run it detached and let the cockpit poll the artifact.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fullstackhouse/cerber",
3
- "version": "0.19.0",
3
+ "version": "0.21.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}.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}
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:152px;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{display:flex;align-items:center;gap:7px;flex-wrap:wrap;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}