mergie-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +91 -0
  3. package/bin/mergie-dev +18 -0
  4. package/bin/mergie.ts +44 -0
  5. package/dist/web/assets/index-4bq8mkyV.css +1 -0
  6. package/dist/web/assets/index-Cp_Gdwf2.js +50 -0
  7. package/dist/web/favicon.svg +12 -0
  8. package/dist/web/index.html +14 -0
  9. package/package.json +68 -0
  10. package/src/cli/args.ts +63 -0
  11. package/src/cli/constants.ts +32 -0
  12. package/src/cli/daemonClient.ts +42 -0
  13. package/src/cli/openBrowser.ts +11 -0
  14. package/src/cli/run.ts +76 -0
  15. package/src/daemon/aiReviewTracker.ts +71 -0
  16. package/src/daemon/allComments.ts +119 -0
  17. package/src/daemon/artifactCapture.ts +47 -0
  18. package/src/daemon/buildUi.ts +54 -0
  19. package/src/daemon/chatPrompt.ts +35 -0
  20. package/src/daemon/chatSocket.ts +78 -0
  21. package/src/daemon/createRegistry.ts +569 -0
  22. package/src/daemon/githubThreads.ts +92 -0
  23. package/src/daemon/inflight.ts +49 -0
  24. package/src/daemon/postMapping.ts +94 -0
  25. package/src/daemon/registry.ts +263 -0
  26. package/src/daemon/reviewPrompt.ts +22 -0
  27. package/src/daemon/reviewService.ts +243 -0
  28. package/src/daemon/router.ts +287 -0
  29. package/src/daemon/server.ts +106 -0
  30. package/src/daemon/splitView.ts +63 -0
  31. package/src/daemon/trpc.ts +20 -0
  32. package/src/db/migrate.ts +24 -0
  33. package/src/db/repositories/aiReviews.ts +113 -0
  34. package/src/db/repositories/artifacts.ts +101 -0
  35. package/src/db/repositories/chatSessions.ts +197 -0
  36. package/src/db/repositories/comments.ts +195 -0
  37. package/src/db/repositories/githubComments.ts +166 -0
  38. package/src/db/repositories/hunkViews.ts +36 -0
  39. package/src/db/repositories/reviewedRanges.ts +75 -0
  40. package/src/db/schema.ts +97 -0
  41. package/src/domain/config.ts +137 -0
  42. package/src/domain/context.ts +32 -0
  43. package/src/domain/diff.ts +178 -0
  44. package/src/domain/entities.ts +92 -0
  45. package/src/domain/fuzzy.ts +43 -0
  46. package/src/domain/generated.ts +33 -0
  47. package/src/domain/hash.ts +0 -0
  48. package/src/domain/lockfiles.ts +20 -0
  49. package/src/domain/paths.ts +59 -0
  50. package/src/domain/ranges.ts +81 -0
  51. package/src/domain/references.ts +36 -0
  52. package/src/domain/url.ts +50 -0
  53. package/src/domain/wordDiff.ts +174 -0
  54. package/src/services/ai.ts +137 -0
  55. package/src/services/exec.ts +44 -0
  56. package/src/services/ghPr.ts +102 -0
  57. package/src/services/ghSearch.ts +135 -0
  58. package/src/services/git.ts +297 -0
  59. package/src/services/github.ts +300 -0
  60. package/src/services/symbols.ts +364 -0
  61. package/src/web/App.tsx +32 -0
  62. package/src/web/components/AiReviewIndicator.tsx +63 -0
  63. package/src/web/components/AiReviewModal.tsx +101 -0
  64. package/src/web/components/AiReviewsPanel.tsx +64 -0
  65. package/src/web/components/ChatPanel.tsx +142 -0
  66. package/src/web/components/CodePreview.tsx +63 -0
  67. package/src/web/components/CommentComposer.tsx +40 -0
  68. package/src/web/components/CommentItem.tsx +59 -0
  69. package/src/web/components/CommentsPanel.tsx +309 -0
  70. package/src/web/components/CommitRail.tsx +64 -0
  71. package/src/web/components/ConfirmButton.tsx +32 -0
  72. package/src/web/components/CopyButton.tsx +33 -0
  73. package/src/web/components/CopyIconButton.tsx +38 -0
  74. package/src/web/components/DiffFrame.tsx +133 -0
  75. package/src/web/components/DiffLines.tsx +149 -0
  76. package/src/web/components/FileFrame.tsx +45 -0
  77. package/src/web/components/FileNavigator.tsx +195 -0
  78. package/src/web/components/FileTree.tsx +45 -0
  79. package/src/web/components/FileView.tsx +53 -0
  80. package/src/web/components/GithubThreadView.tsx +56 -0
  81. package/src/web/components/HunkCard.tsx +121 -0
  82. package/src/web/components/Icons.tsx +215 -0
  83. package/src/web/components/IdentifierMenuPortal.tsx +33 -0
  84. package/src/web/components/PostMenu.tsx +63 -0
  85. package/src/web/components/PrDescription.tsx +29 -0
  86. package/src/web/components/PrLoading.tsx +45 -0
  87. package/src/web/components/PrPicker.tsx +201 -0
  88. package/src/web/components/RangeSelector.tsx +141 -0
  89. package/src/web/components/ResultsList.tsx +159 -0
  90. package/src/web/components/ReviewView.tsx +308 -0
  91. package/src/web/components/RightRail.tsx +146 -0
  92. package/src/web/components/SearchRailPanel.tsx +127 -0
  93. package/src/web/components/Switch.tsx +31 -0
  94. package/src/web/components/SwitchPrModal.tsx +28 -0
  95. package/src/web/components/SymbolLookupMenu.tsx +35 -0
  96. package/src/web/components/Toolbar.tsx +79 -0
  97. package/src/web/components/Tooltip.tsx +72 -0
  98. package/src/web/index.html +13 -0
  99. package/src/web/lib/aiReviewIndicator.ts +29 -0
  100. package/src/web/lib/anchorRow.ts +25 -0
  101. package/src/web/lib/codeSearchFetch.ts +46 -0
  102. package/src/web/lib/commentFilters.ts +36 -0
  103. package/src/web/lib/commentVisibility.ts +90 -0
  104. package/src/web/lib/composerKeys.ts +24 -0
  105. package/src/web/lib/dedupeResults.ts +37 -0
  106. package/src/web/lib/diffMarks.ts +81 -0
  107. package/src/web/lib/fileStatus.ts +12 -0
  108. package/src/web/lib/filterCodeResults.ts +42 -0
  109. package/src/web/lib/filterPrs.ts +38 -0
  110. package/src/web/lib/highlight.ts +40 -0
  111. package/src/web/lib/identifierMenu.ts +41 -0
  112. package/src/web/lib/lineSelection.ts +48 -0
  113. package/src/web/lib/navRouting.ts +40 -0
  114. package/src/web/lib/navStack.ts +109 -0
  115. package/src/web/lib/persistedFlag.ts +43 -0
  116. package/src/web/lib/prPickerModel.ts +27 -0
  117. package/src/web/lib/railState.ts +19 -0
  118. package/src/web/lib/rangeCoverage.ts +27 -0
  119. package/src/web/lib/rangeMap.ts +42 -0
  120. package/src/web/lib/resultCountLabel.ts +11 -0
  121. package/src/web/lib/reviewProgress.ts +26 -0
  122. package/src/web/lib/searchInputsKey.ts +35 -0
  123. package/src/web/lib/searchToken.ts +25 -0
  124. package/src/web/lib/splitSide.ts +13 -0
  125. package/src/web/lib/time.ts +9 -0
  126. package/src/web/lib/togglePrefs.ts +81 -0
  127. package/src/web/lib/useEscToClose.ts +17 -0
  128. package/src/web/lib/useIdentifierMenu.ts +77 -0
  129. package/src/web/lib/useNavLookup.ts +74 -0
  130. package/src/web/lib/usePageTitle.ts +15 -0
  131. package/src/web/lib/visibleFiles.ts +52 -0
  132. package/src/web/main.tsx +24 -0
  133. package/src/web/public/favicon.svg +12 -0
  134. package/src/web/state/useChat.ts +181 -0
  135. package/src/web/state/useCodeSearch.ts +198 -0
  136. package/src/web/state/useReview.ts +138 -0
  137. package/src/web/styles.css +1020 -0
  138. package/src/web/trpc.ts +5 -0
  139. package/tsconfig.json +27 -0
  140. package/vite.config.ts +29 -0
package/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # mergie
2
+
3
+ **Review GitHub pull requests from a fast local web UI, driven by a small CLI.**
4
+
5
+ `mergie` clones a PR locally and serves a focused review interface in your browser — built for
6
+ reading diffs carefully, tracking what you've seen, leaving comments that post back to GitHub, and
7
+ (optionally) getting an AI review. It runs entirely on your machine and reuses your existing `gh`
8
+ login.
9
+
10
+ ![mergie reviewing a pull request](docs/screenshot.png)
11
+
12
+ ## Why
13
+
14
+ GitHub's web review is fine for small PRs, but it forgets what you've read, makes incremental
15
+ review (just the new commits since last time) awkward, and can't help you navigate a large diff.
16
+ `mergie` is built around those gaps:
17
+
18
+ - **Incremental review by commit range** — review only the commits added since you last looked,
19
+ instead of re-reading the whole PR.
20
+ - **Progress you can trust** — mark hunks viewed; a ring shows how many are left, and viewed
21
+ state persists across sessions and refreshes.
22
+ - **Comments that round-trip** — draft locally, then post, edit, or delete on GitHub; inbound
23
+ review threads are synced in.
24
+ - **AI review & chat (optional)** — get an AI pass over a range, or ask questions about a hunk or
25
+ file, powered by the Claude Agent SDK.
26
+ - **Made for big diffs** — fuzzy file search, symbol/code search across the PR, word-level diff
27
+ highlighting, and filters to hide viewed, lock/generated, or whitespace-only changes.
28
+
29
+ ## Requirements
30
+
31
+ - **[Bun](https://bun.sh)** ≥ 1.2 (mergie's runtime).
32
+ - **git** and the **[GitHub CLI](https://cli.github.com) (`gh`)**, authenticated
33
+ (`gh auth login`). mergie reuses `gh`'s token for all API access and your git config for cloning.
34
+ - *(Optional, for AI features)* Claude access for the Claude Agent SDK — e.g. an
35
+ `ANTHROPIC_API_KEY` in your environment.
36
+
37
+ ## Install
38
+
39
+ ```sh
40
+ bun install -g mergie-cli
41
+ # or run without installing:
42
+ bunx mergie-cli --pr https://github.com/withastro/astro/pull/17360
43
+ ```
44
+
45
+ After a global install the command is simply `mergie`.
46
+
47
+ ## Usage
48
+
49
+ ```sh
50
+ mergie # open the home picker (no PR selected)
51
+ mergie --pr https://github.com/withastro/astro/pull/17360 # open a specific PR
52
+ mergie --no-open # start/attach but don't open a browser tab
53
+ mergie reload # restart the daemon (pick up UI changes)
54
+ mergie status # is it running? which PRs are loaded?
55
+ mergie stop # stop the daemon
56
+ ```
57
+
58
+ The URL is parsed tolerantly — trailing `/files`, `/changes`, `#…` are ignored. The first call
59
+ starts a small background daemon that serves the web UI; later calls attach to it. One daemon
60
+ serves **multiple PRs at once**, switchable inside the UI.
61
+
62
+ ## How it works
63
+
64
+ - For each PR, mergie keeps **one reusable local clone** containing both branches, and fetches it
65
+ (via the **Refresh PR** action) to pick up new commits.
66
+ - Durable state — viewed hunks, comments, reviewed ranges, AI results, chat sessions — lives in a
67
+ per-PR **SQLite** database under your data directory, so everything is restored when you reopen a
68
+ PR.
69
+
70
+ ## Configuration
71
+
72
+ - **Data & state:** `$XDG_DATA_HOME/mergie/` (falls back to your platform default).
73
+ - **Config:** a TOML file under `$XDG_CONFIG_HOME/mergie/` for lock-file glob patterns, the
74
+ selectable Claude model list, and AI-review prompt templates.
75
+ - **Port:** the daemon binds **4517**; set `MERGIE_PORT` to change it. Combined with
76
+ `XDG_DATA_HOME`, this lets a second, isolated instance run alongside the first.
77
+
78
+ ## Development
79
+
80
+ ```sh
81
+ git clone https://github.com/ayushpoddar/mergie.git
82
+ cd mergie
83
+ bun install
84
+ bun test # run the test suite
85
+ bun run typecheck # tsc --noEmit
86
+ bun run start # run from source
87
+ ```
88
+
89
+ ## License
90
+
91
+ [GPL-3.0-or-later](LICENSE). © Ayush Poddar.
package/bin/mergie-dev ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env bash
2
+ # Launch a *dev* instance of mergie from this worktree, isolated from the
3
+ # primary daemon so it cannot disturb ongoing review sessions:
4
+ # - its own port (MERGIE_PORT, default 4518 — primary uses 4517)
5
+ # - its own data directory (XDG_DATA_HOME → <worktree>/devdata, so PR
6
+ # databases/clones/artifacts never touch ~/.config/mergie)
7
+ # Config (models, AI-review templates) is shared with the primary instance
8
+ # (read-only) unless XDG_CONFIG_HOME is overridden.
9
+ #
10
+ # Usage: bin/mergie-dev --pr <url> | status | stop
11
+ set -euo pipefail
12
+
13
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
14
+
15
+ export MERGIE_PORT="${MERGIE_PORT:-4518}"
16
+ export XDG_DATA_HOME="${MERGIE_DEV_DATA:-$HERE/devdata}"
17
+
18
+ exec bun run "$HERE/bin/mergie.ts" "$@"
package/bin/mergie.ts ADDED
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env bun
2
+ import { parseArgs } from "@/cli/args.ts";
3
+ import { runCli } from "@/cli/run.ts";
4
+
5
+ const argv: string[] = process.argv.slice(2);
6
+
7
+ if (argv[0] === "__daemon") {
8
+ // Internal: run the daemon in the foreground (spawned detached by the CLI).
9
+ const { startDaemon } = await import("@/daemon/server.ts");
10
+ const { createPrRegistry } = await import("@/daemon/createRegistry.ts");
11
+ const { buildWebUi } = await import("@/daemon/buildUi.ts");
12
+ const { DAEMON_PORT, DIST_DIR, ROOT } = await import("@/cli/constants.ts");
13
+ const registry = createPrRegistry();
14
+
15
+ // On cold start, rebuild the UI when running from a dev checkout (so source
16
+ // changes are picked up); a published install has no build toolchain and
17
+ // ships a prebuilt UI, which buildWebUi serves as-is without rebuilding.
18
+ await buildWebUi({ root: ROOT });
19
+
20
+ /** Flush the stop response, then let in-flight AI work finish before exiting. */
21
+ const shutdown = async (): Promise<void> => {
22
+ await new Promise((resolve) => setTimeout(resolve, 100));
23
+ const drained: boolean = await registry.drainAi(120_000);
24
+ if (!drained) console.error("mergie: timed out waiting for in-flight AI work; exiting.");
25
+ // Grace so a just-completed request's response can flush before we exit.
26
+ await new Promise((resolve) => setTimeout(resolve, 300));
27
+ process.exit(0);
28
+ };
29
+
30
+ const daemon = await startDaemon({
31
+ port: DAEMON_PORT,
32
+ registry,
33
+ requestStop: () => { void shutdown(); },
34
+ staticDir: DIST_DIR,
35
+ });
36
+ console.error(`mergie daemon listening on ${daemon.url}`);
37
+ } else {
38
+ try {
39
+ await runCli(parseArgs(argv));
40
+ } catch (err) {
41
+ console.error(err instanceof Error ? err.message : String(err));
42
+ process.exit(1);
43
+ }
44
+ }
@@ -0,0 +1 @@
1
+ pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.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{--gray-50: #f8f9fb;--gray-100: #f1f3f5;--gray-200: #e6e8eb;--gray-300: #d4d8dd;--gray-400: #b0b6bd;--gray-500: #868e96;--gray-600: #656d76;--gray-700: #464c53;--gray-800: #2b3036;--gray-900: #1a1d21;--accent: #4361ee;--accent-hover: #3b55d9;--accent-tint: #eaeeff;--accent-border: #c7d0fb;--success: #1a7f37;--success-tint: #e7f5ec;--success-tint-hover: #d8eede;--success-border: #b7e0c4;--danger: #cf222e;--danger-hover: #b91c26;--danger-tint: #fdecec;--danger-tint-hover: #fbdcdd;--danger-border: #f3c0c2;--warning: #9a6700;--warning-tint: #fdf3e0;--warning-border: #ecd4a3;--surface: #ffffff;--surface-raised: #ffffff;--surface-sunken: var(--gray-50);--bg: var(--surface);--bg-muted: var(--gray-50);--text: var(--gray-900);--text-secondary: var(--gray-700);--muted: var(--gray-600);--add-bg: #eaf6ee;--add-stripe: #4caf6e;--del-bg: #fdecec;--del-stripe: #e0707a;--add-word-bg: #b8e8c5;--del-word-bg: #f7c2c8;--select-bg: #fff6d6;--border: rgba(0, 0, 0, .09);--border-strong: rgba(0, 0, 0, .14);--radius-control: 6px;--radius-card: 10px;--radius-modal: 14px;--radius-pill: 999px;--sp-1: 4px;--sp-2: 8px;--sp-3: 12px;--sp-4: 16px;--sp-6: 24px;--sp-8: 32px;--shadow-sm: 0 1px 2px rgba(0, 0, 0, .06);--shadow-card: 0 1px 3px rgba(0, 0, 0, .06), 0 1px 2px rgba(0, 0, 0, .04);--shadow-raised: 0 4px 12px rgba(0, 0, 0, .08), 0 1px 3px rgba(0, 0, 0, .06);--shadow-modal: 0 16px 48px rgba(0, 0, 0, .18);--ring: 0 0 0 2px var(--surface), 0 0 0 4px var(--accent);--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;--fs-caption: 12px;--fs-secondary: 13px;--fs-body: 14px;--fs-subhead: 16px;--fs-title: 20px}*{box-sizing:border-box}body{margin:0;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;font-size:var(--fs-body);line-height:1.5;color:var(--text);background:var(--bg);-webkit-font-smoothing:antialiased}code,.diff-lines,.file-name,.hunk-header code{font-family:var(--mono)}:focus-visible{outline:none;box-shadow:var(--ring);border-radius:var(--radius-control)}.icon{display:inline-block;vertical-align:-.125em;flex:none}.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;font:inherit;font-size:var(--fs-secondary);font-weight:500;line-height:1;padding:6px 12px;border-radius:var(--radius-control);cursor:pointer;border:1px solid var(--border-strong);background:var(--surface);color:var(--text);transition:background .12s ease,border-color .12s ease,color .12s ease,box-shadow .12s ease;white-space:nowrap}.btn:hover:not(:disabled){background:var(--gray-100)}.btn:active:not(:disabled){background:var(--gray-200)}.btn:disabled{opacity:.5;cursor:not-allowed}.btn-primary{background:var(--accent);border-color:var(--accent);color:#fff}.btn-primary:hover:not(:disabled){background:var(--accent-hover);border-color:var(--accent-hover)}.btn-primary:active:not(:disabled){background:var(--accent-hover)}.btn-ghost{background:transparent;border-color:transparent;color:var(--text-secondary)}.btn-ghost:hover:not(:disabled){background:var(--gray-100);color:var(--text)}.btn-danger{background:var(--surface);border-color:var(--danger-border);color:var(--danger)}.btn-danger:hover:not(:disabled){background:var(--danger);border-color:var(--danger);color:#fff}.btn-sm{padding:3px 8px;font-size:var(--fs-caption);gap:4px}.btn-accent{border-color:var(--accent-border);color:var(--accent)}.btn-accent:hover:not(:disabled){background:var(--accent-tint)}.btn[aria-pressed=true]{background:var(--accent);border-color:var(--accent);color:#fff}.btn[aria-pressed=true]:hover:not(:disabled){background:var(--accent-hover)}.input,.select,.textarea,input[type=text],input[type=search],select,textarea{font:inherit;font-size:var(--fs-secondary);color:var(--text);background:var(--surface);border:1px solid var(--border-strong);border-radius:var(--radius-control);padding:6px 10px;transition:border-color .12s ease,box-shadow .12s ease}input::placeholder,textarea::placeholder{color:var(--gray-500)}.input:focus,.select:focus,.textarea:focus,input[type=text]:focus,input[type=search]:focus,select:focus,textarea:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-tint)}textarea{resize:vertical}select{appearance:none;-webkit-appearance:none;padding-right:28px;cursor:pointer;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23656d76' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 8px center}input[type=checkbox]{accent-color:var(--accent);width:15px;height:15px;cursor:pointer;margin:0}.review{display:flex;flex-direction:column;height:100vh}.review-header{display:flex;flex-direction:column;gap:var(--sp-3);padding:var(--sp-3) var(--sp-4);border-bottom:1px solid var(--border);background:var(--surface)}.review-header-bar{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--sp-4);flex-wrap:wrap}.review-title{display:flex;flex-direction:column;gap:var(--sp-1);min-width:0}.review-title-row{display:flex;align-items:baseline;gap:var(--sp-3);flex-wrap:wrap}.review-title-row strong{font-size:var(--fs-subhead)}.pr-subtitle{color:var(--muted);font-size:var(--fs-secondary)}.review-nav-link{display:inline-flex;align-items:center;gap:4px;font-size:var(--fs-caption);color:var(--accent);text-decoration:none;font-weight:500}.review-nav-link:hover{text-decoration:underline}.pr-branches{display:flex;align-items:center;gap:4px;flex-wrap:wrap;font-size:var(--fs-caption)}.branch-name{font-family:var(--font-mono, ui-monospace, monospace);font-size:var(--fs-caption);color:var(--text);background:var(--gray-100);border:1px solid var(--border);border-radius:var(--radius-control);padding:1px 6px}.branch-arrow{color:var(--muted);padding:0 2px}.icon-copy-btn{display:inline-flex;align-items:center;justify-content:center;border:1px solid transparent;border-radius:var(--radius-control);background:transparent;color:var(--muted);cursor:pointer;padding:3px;transition:background .12s ease,color .12s ease;flex:none}.icon-copy-btn:hover,.icon-copy-btn:focus-visible{background:var(--gray-100);color:var(--text)}.pr-link-group{display:inline-flex;align-items:center;gap:4px}.review-actions{display:flex;align-items:center;gap:var(--sp-2);flex-wrap:wrap}.review-action-group{display:flex;align-items:center;gap:var(--sp-2)}.review-action-divider{width:1px;height:22px;background:var(--border)}.pr-description-body{padding:var(--sp-3) var(--sp-4)}.pr-description-body h1{font-size:var(--fs-subhead);margin:var(--sp-3) 0 var(--sp-2)}.pr-description-body h2{font-size:var(--fs-body);margin:var(--sp-3) 0 var(--sp-2)}.pr-description-body h3,.pr-description-body h4{font-size:var(--fs-secondary);margin:var(--sp-2) 0}.review-body{display:flex;flex:1;min-height:0}.sidebar{width:320px;flex:none;border-right:1px solid var(--border);display:flex;flex-direction:column;overflow:hidden;background:var(--surface-sunken);transition:width .15s ease}.sidebar.collapsed{width:30px}.sidebar-header{display:flex;justify-content:space-between;align-items:center;padding:var(--sp-3) var(--sp-3) var(--sp-2)}.sidebar-heading{margin:0;padding:0 var(--sp-1);font-size:var(--fs-caption);font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--muted)}.sidebar.collapsed .sidebar-header{justify-content:center;padding:var(--sp-2) 0 0}.sidebar.collapsed .sidebar-heading,.sidebar.collapsed .review-ring-group{display:none}.sidebar-toggle{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:none;border-radius:var(--radius-control);background:transparent;color:var(--muted);cursor:pointer;transition:background .12s ease,color .12s ease}.sidebar-toggle:hover,.sidebar-toggle:focus-visible{background:var(--gray-200);color:var(--text)}.sidebar-content{display:flex;flex-direction:column;flex:1;min-height:0;overflow:hidden}.sidebar.collapsed .sidebar-content{display:none}.diff-area{flex:1;overflow:auto;padding:0 var(--sp-4) var(--sp-4)}.toolbar{display:flex;flex-direction:column;gap:var(--sp-1);padding:0 var(--sp-3) var(--sp-3);border-bottom:1px solid var(--border);font-size:var(--fs-secondary)}.switch-row{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-2);cursor:pointer;padding:4px var(--sp-1);border-radius:var(--radius-control);color:var(--text-secondary)}.switch-row:hover{background:var(--gray-100);color:var(--text)}.switch-label{-webkit-user-select:none;user-select:none}.switch-input{position:absolute;width:1px;height:1px;margin:-1px;padding:0;border:0;clip:rect(0 0 0 0);overflow:hidden;white-space:nowrap}.switch-track{flex:none;position:relative;width:34px;height:20px;border-radius:var(--radius-pill);background:var(--gray-300);transition:background .12s ease}.switch-knob{position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:var(--surface);box-shadow:var(--shadow-sm);transition:transform .12s ease}.switch-input:checked+.switch-track{background:var(--accent)}.switch-input:checked+.switch-track .switch-knob{transform:translate(14px)}.switch-input:focus-visible+.switch-track{box-shadow:var(--ring)}.tooltip-wrap.tooltip-block{display:block}.more-filters{position:relative}.more-filters-btn{display:flex;align-items:center;gap:var(--sp-2);width:100%;padding:4px var(--sp-1);border:none;background:transparent;cursor:pointer;color:var(--text-secondary);font:inherit;font-size:var(--fs-secondary);border-radius:var(--radius-control)}.more-filters-btn:hover{background:var(--gray-100);color:var(--text)}.more-filters-btn>span:first-child{flex:1;text-align:left}.more-filters-btn svg{color:var(--muted)}.more-filters-badge{background:var(--accent);color:#fff;font-size:11px;font-weight:600;min-width:16px;text-align:center;padding:0 5px;border-radius:var(--radius-pill)}.more-filters-pop{position:absolute;z-index:5;top:calc(100% + 4px);left:0;right:0;background:var(--surface);border:1px solid var(--border-strong);border-radius:var(--radius-card);box-shadow:var(--shadow-raised);padding:var(--sp-1)}.review-ring-group{display:flex;align-items:center;gap:var(--sp-2);min-width:0}.review-ring{position:relative;width:34px;height:34px;flex:none}.review-ring svg{position:absolute;inset:0;transform:rotate(-90deg)}.review-ring-track{fill:none;stroke:var(--gray-200)}.review-ring-fill{fill:none;stroke:var(--accent);stroke-linecap:round;transition:stroke-dashoffset .25s ease,stroke .2s ease}.review-ring.done .review-ring-fill{stroke:var(--success)}.review-ring-num{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:var(--fs-secondary);font-weight:700;color:var(--text);font-variant-numeric:tabular-nums}.review-ring.done .review-ring-num{color:var(--success)}.review-ring-caption{font-size:var(--fs-caption);color:var(--muted);line-height:1.2}.range-selector{position:relative;font-size:var(--fs-secondary)}.range-chip{display:inline-flex;align-items:center;gap:6px;cursor:pointer;max-width:420px;border:1px solid var(--border-strong);background:var(--surface);border-radius:var(--radius-pill);padding:5px 12px;font-size:var(--fs-caption);color:var(--text)}.range-chip:hover{background:var(--gray-100)}.range-chip-coverage{font-weight:600;white-space:nowrap;flex:none}.range-chip-sep{color:var(--muted);flex:none}.range-chip-subject{color:var(--text-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chip-caret{color:var(--muted);display:inline-flex;flex:none}.range-panel{position:absolute;right:0;top:38px;z-index:20;width:480px;max-width:84vw;background:var(--surface-raised);border:1px solid var(--border);border-radius:var(--radius-card);box-shadow:var(--shadow-modal);padding:var(--sp-3)}.range-panel-actions{display:flex;align-items:center;gap:var(--sp-3);margin-top:var(--sp-3)}.range-caption{margin:var(--sp-3) 0 0;font-size:var(--fs-caption);color:var(--muted);line-height:1.5}.range-caption code{background:var(--gray-100);padding:1px 4px;border-radius:4px}.range-caption-subject{color:var(--text-secondary);font-style:italic;display:inline-block;max-width:240px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}.reviewed-count{color:var(--muted);font-size:var(--fs-caption)}.range-reviewed-badge{display:inline-flex;align-items:center;gap:5px;color:var(--success);font-weight:600;font-size:var(--fs-secondary);background:var(--success-tint);border:1px solid var(--success-border);border-radius:var(--radius-control);padding:5px 10px;cursor:pointer}.range-reviewed-badge:hover{background:var(--success-tint-hover)}.rail-hint{margin:0 0 var(--sp-2);font-size:var(--fs-caption);color:var(--muted)}.rail-base{font-size:var(--fs-caption);color:var(--muted);padding:2px 8px}.rail-list{list-style:none;margin:0;padding:0;max-height:320px;overflow:auto;border-left:2px solid var(--border)}.rail-row{display:flex;align-items:center;gap:var(--sp-2);width:100%;text-align:left;border:none;background:none;cursor:pointer;padding:6px 8px;font-size:var(--fs-caption);border-left:3px solid transparent;margin-left:-2px;border-radius:0 var(--radius-control) var(--radius-control) 0}.rail-row:hover{background:var(--gray-100)}.rail-row.in-range{background:var(--accent-tint);border-left-color:var(--accent)}.rail-row.range-start,.rail-row.range-end{font-weight:600}.rail-sha{color:var(--accent)}.rail-subject{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rail-time{color:var(--muted);font-variant-numeric:tabular-nums}.file-tree{overflow:auto;flex:1}.file-search-wrap{position:relative;margin:var(--sp-3)}.file-search-wrap .icon{position:absolute;left:9px;top:50%;transform:translateY(-50%);color:var(--gray-500);pointer-events:none}.file-search-wrap .file-search{width:100%;padding:6px 10px 6px 30px}.file-tree ul{list-style:none;margin:0;padding:0 var(--sp-2) var(--sp-2)}.file-item a{display:flex;align-items:center;gap:var(--sp-2);padding:6px 8px;border-radius:var(--radius-control);text-decoration:none;color:var(--text);font-size:var(--fs-caption)}.file-item a:hover{background:var(--gray-100)}.file-item.viewed a{color:var(--muted)}.file-flag{width:14px;display:inline-flex;color:var(--success);flex:none}.file-status-icon{display:inline-flex;flex:none}.file-status-icon.added{color:var(--success)}.file-status-icon.modified{color:var(--warning)}.file-status-icon.deleted{color:var(--danger)}.file-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.file-hunkcount{color:var(--muted);font-size:11px;background:var(--gray-100);border-radius:var(--radius-pill);padding:1px 7px;min-width:20px;text-align:center}.file-section{margin-bottom:var(--sp-8)}.file-heading{font-size:var(--fs-body);font-weight:600;height:32px;box-sizing:border-box;margin:0;padding:0 var(--sp-2);display:flex;align-items:center;gap:var(--sp-2);border-bottom:1px solid var(--border);background:var(--surface);position:sticky;top:0;z-index:3}.file-heading small{color:var(--muted);font-weight:400;font-size:var(--fs-caption)}.file-heading-actions{display:inline-flex;align-items:center;gap:0}.hunk-card{border:1px solid var(--border);border-radius:var(--radius-card);margin:var(--sp-3) 0;background:var(--surface);box-shadow:var(--shadow-card);clip-path:inset(0 round var(--radius-card))}.hunk-card.viewed,.hunk-card.viewed .hunk-header{background:var(--surface-sunken)}.hunk-header{display:flex;align-items:center;gap:var(--sp-2);background:var(--surface-sunken);padding:6px 10px;font-size:var(--fs-caption);border-bottom:1px solid var(--border);position:sticky;top:32px;z-index:2}.hunk-collapse{border:none;background:none;cursor:pointer;color:var(--muted);padding:2px;display:inline-flex;align-items:center;border-radius:4px}.hunk-collapse:hover{background:var(--gray-200);color:var(--text)}.hunk-title{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--muted);font-family:var(--mono)}.hunk-actions{display:inline-flex;align-items:center;gap:2px;flex:none}.hunk-action{position:relative;display:inline-flex;align-items:center;justify-content:center;border:1px solid transparent;border-radius:var(--radius-control);background:transparent;color:var(--text-secondary);cursor:pointer;padding:4px;font-size:var(--fs-caption);transition:background .12s ease,color .12s ease}.hunk-action:hover,.hunk-action:focus-visible{background:var(--gray-100);color:var(--text)}.hunk-viewed{display:inline-flex;align-items:center;gap:5px;cursor:pointer;white-space:nowrap;font-size:var(--fs-caption);color:var(--text-secondary)}.hunk-card.viewed .hunk-viewed{color:var(--success);font-weight:500}.diff-lines{border-collapse:collapse;width:100%;font-size:13px;line-height:1.55}.diff-lines td{padding:1px 8px;vertical-align:top;white-space:pre-wrap}.diff-lines .gutter{color:var(--gray-500);text-align:right;-webkit-user-select:none;user-select:none;width:1%;padding:1px 6px}.diff-lines .marker{-webkit-user-select:none;user-select:none;width:1ch;padding-left:4px;padding-right:0;color:var(--gray-500)}.line-add{background:var(--add-bg);box-shadow:inset 3px 0 0 var(--add-stripe)}.line-add .marker{color:var(--success)}.line-del{background:var(--del-bg);box-shadow:inset 3px 0 0 var(--del-stripe)}.line-del .marker{color:var(--danger)}.line.selected{background:var(--select-bg)}.diff-lines.dragging{-webkit-user-select:none;user-select:none;cursor:crosshair}mark.diff-word{background:none;color:inherit;border-radius:2px}.line-add mark.diff-word,.split-add mark.diff-word{background:var(--add-word-bg)}.line-del mark.diff-word,.split-del mark.diff-word{background:var(--del-word-bg)}.line-act{width:22px;padding:0;text-align:center}.add-comment{opacity:0;border:none;background:var(--accent);color:#fff;cursor:pointer;border-radius:5px;width:17px;height:17px;line-height:1;font-size:13px;padding:0;transition:opacity .1s ease,transform .1s ease}.add-comment:hover{transform:scale(1.1)}.line:hover .add-comment{opacity:1}.comment-row{position:relative;z-index:3;scroll-margin-top:88px}.comment-row td{background:var(--surface-sunken);padding:var(--sp-2) var(--sp-3)!important}.comment-item{border:1px solid var(--border);border-radius:var(--radius-card);background:var(--surface);padding:var(--sp-2) var(--sp-3);margin:var(--sp-1) 0;box-shadow:var(--shadow-sm)}.hunk-level-comment{position:relative;z-index:3;padding:var(--sp-2) var(--sp-3);background:var(--surface)}.comment-meta{display:flex;align-items:center;gap:var(--sp-2);font-size:11px;color:var(--muted);margin-bottom:var(--sp-1);flex-wrap:wrap}.comment-tools{margin-left:auto;display:flex;gap:4px;align-items:center}.comment-body{font-size:var(--fs-secondary)}.comment-body p{margin:var(--sp-1) 0}.comment-body pre{background:var(--surface-sunken);padding:var(--sp-2) var(--sp-3);border-radius:var(--radius-control);overflow-x:auto;border:1px solid var(--border);font-size:var(--fs-caption)}.comment-body pre code{white-space:pre}.comment-body code:not(pre code){background:var(--gray-100);padding:1px 5px;border-radius:4px;font-size:.92em}.comment-composer{display:flex;flex-direction:column;gap:var(--sp-2)}.comment-textarea{width:100%;min-height:72px;font-family:inherit}.comment-actions{display:flex;gap:var(--sp-2)}.notice{color:var(--muted);font-style:italic;font-size:var(--fs-secondary)}.file-heading-name{flex:1;overflow:hidden;text-overflow:ellipsis}.modal-overlay{position:fixed;inset:0;background:#17191c73;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px);display:flex;align-items:center;justify-content:center;z-index:100}.modal{background:var(--surface);border-radius:var(--radius-modal);box-shadow:var(--shadow-modal);display:flex;flex-direction:column;overflow:hidden}.modal.full-file{width:94vw;height:90vh}.modal-header{display:flex;align-items:center;gap:var(--sp-3);padding:var(--sp-3) var(--sp-4);border-bottom:1px solid var(--border);background:var(--surface)}.modal-header strong{font-size:var(--fs-subhead)}.modal-title-strong{display:inline-flex;align-items:center;gap:6px}.modal-header-spacer{flex:1}.modal-title{flex:1;min-width:0;display:flex;align-items:center;gap:var(--sp-2);font-family:var(--mono);font-size:var(--fs-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.modal-title .icon{color:var(--muted)}.modal-close{border:none;background:none;cursor:pointer;color:var(--muted);display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:var(--radius-control);flex:none}.modal-close:hover{background:var(--gray-100);color:var(--text)}.split-base-head{display:inline-flex;align-items:center;gap:6px;font-size:var(--fs-caption);color:var(--muted);font-family:var(--mono)}.split-base-head code{background:var(--gray-100);padding:1px 5px;border-radius:4px}.split-labels{display:flex;gap:0;color:var(--muted);font-size:var(--fs-caption);border-bottom:1px solid var(--border);background:var(--surface-sunken)}.split-labels span{flex:1;text-align:center;padding:5px 0;font-weight:500}.split-labels span:first-child{border-right:1px solid var(--border)}.nav-code,.nav-slot{flex:1;min-height:0;display:flex;flex-direction:column}.nav-code.hidden,.nav-slot.hidden{display:none}.full-file-body{overflow:auto;flex:1}.split-table{border-collapse:collapse;width:100%;font-size:13px;line-height:1.55;table-layout:fixed}.split-table td{padding:1px 8px;vertical-align:top;white-space:pre-wrap;word-break:break-all}.split-table td.split-code:first-of-type,.split-no:first-child{border-right:1px solid var(--border)}.split-no{width:48px;text-align:right;color:var(--gray-500);-webkit-user-select:none;user-select:none}.split-code{font-family:var(--mono)}.split-del{background:var(--del-bg);box-shadow:inset 3px 0 0 var(--del-stripe)}.split-add{background:var(--add-bg);box-shadow:inset 3px 0 0 var(--add-stripe)}.split-empty{background:var(--surface-sunken)}@keyframes split-anchor-fade{0%{box-shadow:inset 0 0 0 2px var(--accent);background:var(--accent-tint)}to{box-shadow:inset 0 0 0 2px transparent;background:transparent}}.split-anchor td{animation:split-anchor-fade 1.1s ease-out}.modal-empty-side{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--sp-2);min-height:240px;color:var(--muted);text-align:center;padding:var(--sp-8)}.modal-empty-side .icon{color:var(--gray-400)}.all-comments{padding:var(--sp-6);max-width:900px;margin:0 auto;display:flex;flex-direction:column;gap:var(--sp-4)}.all-comments-header{display:flex;flex-direction:column;gap:var(--sp-2)}.all-comments-header h1{font-size:var(--fs-title);margin:0}.all-comments-header a{display:inline-flex;align-items:center;gap:4px;font-size:var(--fs-caption);color:var(--accent);text-decoration:none;align-self:flex-start}.all-comments-header a:hover{text-decoration:underline}.comment-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--sp-2)}.comment-list-item{border:1px solid var(--border);border-radius:var(--radius-card);padding:var(--sp-3) var(--sp-4);background:var(--surface);box-shadow:var(--shadow-card)}.comment-list-meta{display:flex;align-items:center;gap:var(--sp-3);font-size:var(--fs-caption);color:var(--muted);flex-wrap:wrap}.comment-list-body{margin:var(--sp-2) 0;font-size:var(--fs-secondary)}.comment-list-actions{display:flex;gap:var(--sp-2);align-items:center;font-size:var(--fs-caption)}.comment-edit{margin:var(--sp-2) 0}.comment-edit .comment-textarea{width:100%;min-height:64px}.comment-edit-actions{display:flex;gap:var(--sp-2);align-items:center;font-size:var(--fs-caption);margin-top:var(--sp-1)}.badge{display:inline-flex;align-items:center;gap:4px;border:1px solid var(--border);border-radius:var(--radius-pill);padding:1px 9px;font-size:11px;font-weight:500;white-space:nowrap}.badge.posted,.badge.origin-posted{background:var(--success-tint);border-color:var(--success-border);color:var(--success)}.badge.origin-local{background:var(--gray-100);border-color:var(--border);color:var(--muted)}.badge.origin-github{background:var(--accent-tint);border-color:var(--accent-border);color:var(--accent)}.comment-author{font-weight:600;color:var(--text)}.reply-count{color:var(--muted);font-style:italic}.disclosure>summary{list-style:none;cursor:pointer;display:flex;align-items:flex-start;gap:6px}.disclosure>summary::-webkit-details-marker{display:none}.disclosure-chevron{flex:none;margin-top:2px;color:var(--muted);transition:transform .12s ease}.disclosure[open]>summary>.disclosure-chevron{transform:rotate(90deg)}.right-rail{flex:none;width:48px;display:flex;flex-direction:column;gap:var(--sp-1);align-items:center;padding:var(--sp-2) 0;border-left:1px solid var(--border);background:var(--surface)}.rail-item{position:relative}.rail-btn{position:relative;width:34px;height:34px;display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;border:1px solid transparent;border-radius:var(--radius-control);background:none;color:var(--text-secondary);cursor:pointer;padding:0;transition:background .12s ease,color .12s ease}.rail-btn:hover,.rail-btn:focus-visible{background:var(--gray-100);color:var(--text)}.rail-btn.active{background:var(--accent);border-color:var(--accent);color:#fff}.rail-btn.active:hover,.rail-btn.active:focus-visible{background:var(--accent);border-color:var(--accent);color:#fff}.tooltip-wrap{display:inline-flex}.tooltip{position:fixed;z-index:100;pointer-events:none;background:var(--gray-900);color:#fff;border-radius:var(--radius-control);padding:4px 8px;font-size:var(--fs-caption);font-weight:500;line-height:1.3;white-space:nowrap;box-shadow:var(--shadow-raised);animation:tooltip-fade-in .04s ease-out}.tooltip-left{transform:translate(-100%,-50%)}.tooltip-bottom{transform:translate(-50%)}@keyframes tooltip-fade-in{0%{opacity:0}to{opacity:1}}.rail-badge{position:absolute;top:-3px;right:-3px;min-width:16px;height:16px;padding:0 4px;box-sizing:border-box;border-radius:var(--radius-pill);background:var(--accent);color:#fff;border:1.5px solid var(--surface);font-size:10px;font-weight:600;line-height:13px;text-align:center;z-index:1}.rail-btn.active .rail-badge{background:#fff;color:var(--accent);border-color:var(--accent)}.rail-sidebar{width:440px;flex:none;border-left:1px solid var(--border);display:flex;flex-direction:column;overflow:hidden;background:var(--surface-sunken);position:relative}.rail-sidebar[hidden]{display:none}.rail-sidebar-header{display:flex;align-items:center;gap:var(--sp-3);padding:var(--sp-3);border-bottom:1px solid var(--border);background:var(--surface)}.rail-sidebar-header strong{flex:1;font-size:var(--fs-subhead)}.rail-sidebar-body{flex:1;min-height:0;display:flex}.rail-panel{flex:1;min-width:0;display:flex;flex-direction:column;overflow:auto}.rail-panel[hidden]{display:none}.comments-panel-content{display:flex;flex-direction:column;flex:1;min-height:0}.comments-panel-count{padding:var(--sp-2) var(--sp-3) 0}.comments-panel-filters{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-2);padding:var(--sp-3);border-bottom:1px solid var(--border);background:var(--surface);font-size:var(--fs-caption)}.comments-panel-filters label{display:inline-flex;align-items:center;gap:5px;color:var(--muted)}.comments-panel-filters select{max-width:150px}.ai-reviews-list{padding:var(--sp-3)}.segmented{display:inline-flex;border:1px solid var(--border-strong);border-radius:var(--radius-control);overflow:hidden;background:var(--surface)}.segmented-item{font:inherit;font-size:var(--fs-caption);color:var(--text-secondary);border:none;background:none;padding:4px 10px;cursor:pointer;border-left:1px solid var(--border);transition:background .12s ease,color .12s ease}.segmented-item:first-child{border-left:none}.segmented-item:hover:not(.active){background:var(--gray-100)}.segmented-item.active{background:var(--accent);color:#fff}.segmented>.tooltip-wrap{border-left:1px solid var(--border)}.segmented>.tooltip-wrap:first-child{border-left:none}.comments-panel-content .comment-list{overflow:auto;flex:1;padding:var(--sp-2) var(--sp-3) var(--sp-3)}.comments-panel-content .comment-list-item{background:var(--surface)}.comments-panel-content .comment-list-meta code{max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background:var(--gray-100);padding:1px 5px;border-radius:4px}.comment-jump{display:block;width:100%;text-align:left;border:none;background:none;padding:0;margin:0;cursor:pointer;font:inherit}.comment-jump:hover .comment-list-meta code{text-decoration:underline}@keyframes comment-flash-fade{0%{background:#fff3bf;box-shadow:0 0 0 3px #ffe066}to{background:transparent;box-shadow:0 0 0 3px transparent}}.comment-flash{animation:comment-flash-fade 1.8s ease-out;border-radius:var(--radius-card)}.oor-prompt{position:absolute;left:var(--sp-3);right:var(--sp-3);bottom:var(--sp-3);z-index:5;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-card);box-shadow:var(--shadow-raised);padding:var(--sp-3) var(--sp-4)}.oor-text{margin:0 0 var(--sp-3);font-size:var(--fs-caption);line-height:1.5}.oor-text code{background:var(--gray-100);padding:1px 4px;border-radius:4px}.oor-actions{display:flex;align-items:center;gap:var(--sp-3)}.oor-open{display:inline-flex;align-items:center;gap:4px;font-size:var(--fs-caption);color:var(--accent);text-decoration:none}.oor-open:hover{text-decoration:underline}.post-menu{display:inline-flex;align-items:center;gap:var(--sp-2);flex-wrap:wrap}.post-menu-hint{font-size:11px;color:var(--muted)}.post-busy{font-size:11px;color:var(--muted);font-style:italic}.post-warning{color:var(--warning);font-size:11px}.gh-thread{border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:var(--radius-card);background:var(--surface);padding:var(--sp-2) var(--sp-3);margin:var(--sp-1) 0;box-shadow:var(--shadow-sm)}.gh-thread-tag{display:inline-flex;align-items:center;gap:4px;font-size:10px;text-transform:uppercase;letter-spacing:.04em;color:var(--accent);margin-bottom:var(--sp-1);font-weight:600}.gh-comment{border-top:1px solid var(--border);padding-top:var(--sp-2);margin-top:var(--sp-2)}.gh-comment:first-of-type{border-top:none;padding-top:0;margin-top:0}.gh-comment .comment-meta strong{color:var(--text)}.symbol-menu{position:fixed;z-index:120;display:inline-flex;align-items:center;gap:var(--sp-2);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-card);box-shadow:var(--shadow-raised);padding:5px 8px;font-size:var(--fs-caption)}.symbol-menu-term{background:var(--gray-100);padding:2px 6px;border-radius:4px;max-width:160px;overflow:hidden;text-overflow:ellipsis}.symbol-menu-side{display:inline-flex;border:1px solid var(--border-strong);border-radius:var(--radius-control);overflow:hidden}.symbol-menu-side button{border:none;background:var(--surface);font:inherit;font-size:var(--fs-caption);padding:3px 9px;cursor:pointer;color:var(--text-secondary)}.symbol-menu-side button:hover{background:var(--gray-100)}.symbol-menu-side button.active{background:var(--accent);color:#fff}.symbol-side-tag{font-size:10px;text-transform:uppercase;color:var(--muted);border:1px solid var(--border);border-radius:var(--radius-pill);padding:1px 8px}.symbol-panel-close{border:none;background:none;cursor:pointer;color:var(--muted);display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:var(--radius-control);flex:none}.symbol-panel-close:hover{background:var(--gray-100);color:var(--text)}.search-rail-panel{display:flex;flex-direction:column;flex:1;min-height:0}.search-rail-controls{display:flex;flex-direction:column;gap:var(--sp-2);padding:var(--sp-3);border-bottom:1px solid var(--border);background:var(--surface)}.search-rail-query{width:100%}.search-rail-row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-3);font-size:var(--fs-caption)}.search-rail-run{display:flex;align-items:center;gap:var(--sp-2);flex-wrap:wrap}.search-rail-stale{font-size:var(--fs-caption);color:var(--accent)}.results-list{display:flex;flex-direction:column;flex:1;min-height:0}.results-list:focus{outline:none}.results-header{display:flex;align-items:center;gap:var(--sp-2);padding:var(--sp-2) var(--sp-3);font-size:var(--fs-caption);border-bottom:1px solid var(--border);background:var(--surface)}.results-header strong{font-size:var(--fs-subhead)}.results-header code{overflow:hidden;text-overflow:ellipsis;background:var(--gray-100);padding:1px 5px;border-radius:4px}.results-count{margin-left:auto;color:var(--muted)}.results-scope{display:inline-flex;align-items:center;gap:6px;color:var(--muted)}.results-scope code{background:var(--gray-100);padding:1px 5px;border-radius:4px}.results-note{display:flex;align-items:center;gap:6px;margin:0;padding:var(--sp-1) var(--sp-3);font-size:var(--fs-caption);color:var(--muted);background:var(--surface-sunken);border-bottom:1px solid var(--border)}.results-note-info{display:inline-flex;align-items:center;padding:0;border:0;background:none;color:var(--muted);cursor:help}.results-note-info:hover{color:var(--accent)}.results-filters{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-2);padding:var(--sp-2) var(--sp-3);border-bottom:1px solid var(--border);font-size:var(--fs-caption)}.results-filters input[type=text]{flex:1;min-width:90px}.results-toggle{display:inline-flex;align-items:center;gap:5px;color:var(--muted)}.results-error{color:var(--danger)}.results-items{list-style:none;margin:0;padding:0;overflow:auto;flex:1}.results-item{border-bottom:1px solid var(--border);padding:var(--sp-2) var(--sp-3)}.results-item.focused{background:var(--accent-tint)}.results-item-loc{display:flex;align-items:center;gap:var(--sp-2);font-size:var(--fs-caption);margin-bottom:var(--sp-1)}.results-item-loc code{overflow:hidden;text-overflow:ellipsis}.results-item-scope{font-size:10px;color:var(--muted);border:1px solid var(--border);border-radius:var(--radius-pill);padding:1px 7px;white-space:nowrap;margin-left:auto}.code-preview{border-collapse:collapse;width:100%;font-family:var(--mono);font-size:11px;line-height:1.5;table-layout:fixed;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-control);overflow:hidden}.code-preview td{padding:0 8px;vertical-align:top;white-space:pre-wrap;word-break:break-all}.code-preview-no{width:44px;text-align:right;color:var(--muted);-webkit-user-select:none;user-select:none;border-right:1px solid var(--border)}.code-preview-line.matched{background:var(--accent-tint)}.code-preview-line.matched .code-preview-no{color:var(--accent);font-weight:600}.chat-panel{width:420px;border-left:1px solid var(--border);display:flex;flex-direction:column;background:var(--surface-sunken)}.chat-panel-header{display:flex;align-items:center;gap:var(--sp-2);padding:var(--sp-3);border-bottom:1px solid var(--border);background:var(--surface)}.chat-panel-header strong{display:inline-flex;align-items:center;gap:5px}.chat-scope{flex:1;font-size:var(--fs-caption);color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chat-controls{display:flex;gap:var(--sp-2);padding:var(--sp-3);border-bottom:1px solid var(--border)}.chat-controls select{flex:1}.chat-transcript{flex:1;overflow:auto;padding:var(--sp-3);display:flex;flex-direction:column;gap:var(--sp-3);scroll-behavior:smooth}.chat-msg{border:1px solid var(--border);border-radius:var(--radius-card);padding:var(--sp-2) var(--sp-3);background:var(--surface);box-shadow:var(--shadow-sm)}.chat-user{background:var(--accent-tint);border-color:var(--accent-border);align-self:flex-end;max-width:92%}.chat-assistant{align-self:flex-start;max-width:98%}.chat-msg-head{display:flex;align-items:center;gap:var(--sp-2);margin-bottom:var(--sp-1)}.chat-role{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--muted)}.chat-time{font-size:10px;color:var(--muted)}.chat-copy{margin-left:auto;opacity:0;transition:opacity .12s}.chat-msg:hover .chat-copy{opacity:1}.chat-error{color:var(--danger)}.chat-msg .comment-body pre{overflow-x:auto}.chat-working{display:flex;align-items:center;gap:var(--sp-2);font-size:var(--fs-caption);color:var(--muted);margin-top:var(--sp-2)}.chat-working-label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chat-elapsed{font-variant-numeric:tabular-nums;font-size:11px;opacity:.8}.chat-spinner{width:12px;height:12px;border:2px solid var(--gray-300);border-top-color:var(--accent);border-radius:50%;animation:chat-spin .7s linear infinite;flex:none}@keyframes chat-spin{to{transform:rotate(360deg)}}.chat-input{display:flex;flex-direction:column;gap:var(--sp-2);padding:var(--sp-3);border-top:1px solid var(--border);background:var(--surface)}.chat-input button{align-self:flex-end}.chat-artifacts{border-top:1px solid var(--border);padding:var(--sp-2) var(--sp-3);background:var(--surface);font-size:var(--fs-caption)}.chat-artifacts ul{list-style:none;margin:var(--sp-1) 0 0;padding:0}.chat-artifacts a{color:var(--accent);text-decoration:none}.ai-review-modal{width:820px;max-width:92vw;max-height:88vh}.ai-review-body{overflow:auto;padding:var(--sp-4) var(--sp-6)}.ai-review-form{display:flex;flex-direction:column;gap:var(--sp-3);margin-bottom:var(--sp-4)}.ai-review-form label{display:flex;flex-direction:column;gap:4px;font-size:var(--fs-caption);color:var(--muted)}.ai-review-form select{align-self:flex-start;min-width:240px}.ai-review-form button{align-self:flex-start}.ai-review-past{border-top:1px solid var(--border);padding-top:var(--sp-3)}.ai-review-item{margin:var(--sp-2) 0}.ai-review-item summary{cursor:pointer;font-size:var(--fs-caption);color:var(--muted)}.ai-review-running{display:flex;align-items:center;gap:var(--sp-2);font-style:normal}.ai-indicator{display:inline-flex;align-items:center;gap:5px;font:inherit;font-size:var(--fs-caption);font-weight:500;padding:5px 10px;border-radius:var(--radius-pill);white-space:nowrap;border:1px solid transparent}button.ai-indicator{cursor:pointer}.ai-indicator-running{background:var(--accent-tint);border-color:var(--accent-border);color:var(--accent)}.ai-indicator-done{background:var(--success-tint);border-color:var(--success-border);color:var(--success)}.ai-indicator-done:hover{background:var(--success-tint-hover)}.ai-indicator-failed{background:var(--danger-tint);border-color:var(--danger-border);color:var(--danger)}.ai-indicator-failed:hover{background:var(--danger-tint-hover)}.ai-indicator .chat-spinner{width:12px;height:12px}.confirm-inline{display:inline-flex;align-items:center;gap:var(--sp-2)}.confirm-warn{color:var(--warning);font-size:11px}.badge.stale{background:var(--warning-tint);border-color:var(--warning-border);color:var(--warning)}.reviewed-list{list-style:none;margin:var(--sp-3) 0 0;padding:var(--sp-2) 0 0;border-top:1px solid var(--border);display:flex;flex-direction:column;gap:var(--sp-1)}.reviewed-list li{display:flex;align-items:center;gap:var(--sp-2)}.reviewed-range{border:1px solid var(--border-strong);background:var(--surface);border-radius:var(--radius-control);padding:3px 9px;cursor:pointer;font-size:var(--fs-caption)}.reviewed-range:hover:not(:disabled){background:var(--gray-100)}.reviewed-range:disabled{opacity:.5;cursor:not-allowed;text-decoration:line-through}.landing{max-width:760px;margin:0 auto;padding:56px var(--sp-6) var(--sp-8)}.landing-brand{display:flex;align-items:center;gap:var(--sp-2);margin-bottom:var(--sp-1)}.landing-wordmark{font-size:26px;font-weight:700;letter-spacing:-.01em;margin:0}.landing-mark{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:9px;background:var(--accent);color:#fff}.landing-tagline{color:var(--muted);margin:0 0 var(--sp-6);font-size:var(--fs-subhead)}.pr-cards{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--sp-2)}.pr-card{display:flex;flex-direction:column;gap:4px;padding:var(--sp-4);border:1px solid var(--border);border-radius:var(--radius-card);background:var(--surface);box-shadow:var(--shadow-card);text-decoration:none;color:var(--text);transition:border-color .12s ease,box-shadow .12s ease,transform .06s ease}.pr-card:hover{border-color:var(--accent-border);box-shadow:var(--shadow-raised)}.pr-card:active{transform:translateY(1px)}.pr-card-meta{display:flex;align-items:center;gap:var(--sp-2);font-size:var(--fs-caption);color:var(--muted)}.pr-card-repo{font-family:var(--mono)}.pr-card-num{color:var(--accent);font-weight:600}.pr-card-title{font-size:var(--fs-subhead);font-weight:600}.pr-card-author{font-size:var(--fs-caption);color:var(--muted)}.picker{display:flex;flex-direction:column;gap:var(--sp-4)}.picker-url{display:flex;flex-wrap:wrap;gap:var(--sp-2);align-items:center}.picker-url-input{flex:1;min-width:240px}.picker-url-error{flex-basis:100%;margin:0;font-size:var(--fs-caption);color:var(--danger)}.picker-filter{position:relative;display:flex;align-items:center}.picker-filter .icon{position:absolute;left:9px;color:var(--gray-500);pointer-events:none}.picker-filter .picker-filter-input{width:100%;padding:6px 10px 6px 30px}.picker-section{display:flex;flex-direction:column;gap:var(--sp-2)}.picker-section-head{display:flex;align-items:center;justify-content:space-between}.picker-section-title{margin:0;font-size:var(--fs-caption);font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--muted)}.pr-chip{display:inline-flex;align-items:center;padding:1px 7px;border-radius:999px;font-size:11px;font-weight:600;background:var(--gray-100);color:var(--text-secondary);border:1px solid var(--border)}.pr-chip-draft{background:var(--gray-200);color:var(--muted)}.pr-chip-current{background:var(--accent-soft, var(--gray-100));color:var(--accent);border-color:var(--accent-border)}.pr-card-current{opacity:.75;cursor:default;box-shadow:none}.pr-card-current:hover{border-color:var(--border);box-shadow:none}.picker-status{display:flex;align-items:center;gap:var(--sp-2);padding:var(--sp-4);color:var(--muted);font-size:var(--fs-secondary)}.picker-error{color:var(--danger)}.pr-loading,.pr-loading-fail{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--sp-2);text-align:center;padding:72px var(--sp-6);color:var(--muted)}.pr-loading .chat-spinner{width:22px;height:22px}.pr-loading-fail .btn{margin-top:var(--sp-3)}.picker-modal{width:min(680px,92vw);max-height:86vh}.picker-modal-body{padding:var(--sp-4);overflow-y:auto}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--sp-2);text-align:center;padding:64px var(--sp-6);color:var(--muted)}.empty-state .icon{color:var(--gray-400)}.empty-state-title{font-size:var(--fs-subhead);font-weight:600;color:var(--text-secondary);font-style:normal;margin:var(--sp-1) 0 0}.empty-state-hint{font-size:var(--fs-secondary);max-width:360px}.diff-loading{display:flex;align-items:center;gap:var(--sp-2);padding:var(--sp-4);color:var(--muted)}.diff-loading .chat-spinner{width:16px;height:16px}