agent-feedbacker 0.2.0 → 0.3.1
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/.claude-plugin/plugin.json +2 -2
- package/README.md +22 -10
- package/bin/feedbacker.mjs +12 -2
- package/package.json +4 -4
- package/skills/review/SKILL.md +5 -3
- package/src/core.mjs +88 -9
- package/src/dist/app.css +1 -1
- package/src/dist/app.js +13 -12
- package/src/mcp.mjs +20 -3
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "feedbacker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Review an agent's changes across several git repos in a local MR-style page and send line comments back to it.",
|
|
5
5
|
"author": { "name": "Pavel Polivka" },
|
|
6
|
-
"repository": "https://github.com/
|
|
6
|
+
"repository": "https://github.com/polivkadev/feedbacker",
|
|
7
7
|
"license": "MIT"
|
|
8
8
|
}
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ A local, merge-request-style review page for coding agents. The agent passes the
|
|
|
14
14
|
### Claude Code plugin
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
claude plugin marketplace add
|
|
17
|
+
claude plugin marketplace add polivkadev/feedbacker
|
|
18
18
|
claude plugin install feedbacker@feedbacker
|
|
19
19
|
```
|
|
20
20
|
|
|
@@ -48,8 +48,9 @@ The npm package is `agent-feedbacker`; the command is `feedbacker`. Node 22 or n
|
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
50
|
feedbacker ~/Projects/api ~/Projects/web@main \
|
|
51
|
-
-m
|
|
52
|
-
-
|
|
51
|
+
-m $'Bind the API to localhost.\n\n- New `loadConfig()` reads HOST and PORT\n- README documents the port' \
|
|
52
|
+
-f 'src/server.ts:5-12=Where the host is bound' \
|
|
53
|
+
-f 'src/config.ts=Defaults live here'
|
|
53
54
|
```
|
|
54
55
|
|
|
55
56
|
- `repo` diffs the working tree (staged, unstaged and untracked files) against `HEAD`.
|
|
@@ -65,9 +66,11 @@ feedbacker ~/Projects/api ~/Projects/web@main \
|
|
|
65
66
|
|
|
66
67
|
In the browser:
|
|
67
68
|
|
|
69
|
+
- The agent's summary is short markdown, with a lead sentence and a list. Under it, **Where to look** lists the places the agent thinks matter most; each links to the file, and the agent's note also appears in the diff above those lines.
|
|
70
|
+
- Files are sorted by kind: the agent's focus first, then **Code**, then **Tests**, **Config**, **Docs** and **Generated** (lock files, build output, minified and binary files). The kind comes from the path and works across languages (`*.spec.ts`, `*_test.go`, `test_*.py`, `FooTest.java`, `__tests__/`, `CLAUDE.md`, …). Only Code starts expanded; the chips above the diffs open or close a whole kind.
|
|
68
71
|
- In changed lines, the changed words are marked.
|
|
69
72
|
- Tick **Viewed** on a file when you're done with it: it collapses, dims in the file tree, and the top bar counts how many you've viewed.
|
|
70
|
-
- Click a line number to comment on that line (a highlighter "+" shows where); shift-click a second one to
|
|
73
|
+
- Click a line number to comment on that line (a highlighter "+" shows where); shift-click a second one to cover a range. Then **Comment** (something to change) or **Ask** (a question the agent must answer). Ctrl+Enter comments, Ctrl+Shift+Enter asks, Esc cancels.
|
|
71
74
|
- With several repos, the picker in the top bar switches between all of them and a single one (the summary, questions and your comments in other repos stay). The top bar also shows the size of the change and keeps **Approve** and **Request changes** in reach; files can be collapsed, and the file tree shows where your comments are.
|
|
72
75
|
- Comments, answers and the general comment are kept in the browser's local storage until you submit, so a refresh loses nothing.
|
|
73
76
|
- **Approve** or **Request changes** sends everything to the agent and closes the server.
|
|
@@ -79,8 +82,9 @@ In the browser:
|
|
|
79
82
|
| Argument | Type | |
|
|
80
83
|
|---|---|---|
|
|
81
84
|
| `repos` | `[{ path, base?, worktrees? }]` | Required. Absolute paths; `base` defaults to `HEAD` (or, with the plugin's hooks, where the repo stood when the session started); `worktrees: true` adds all worktrees of that repo. (`exact` and `session` are set by the hook.) |
|
|
82
|
-
| `summary` | string |
|
|
83
|
-
| `
|
|
85
|
+
| `summary` | string | Markdown: paragraphs, `- ` lists, `` `code` ``, `**bold**`, `### ` subheadings. The first paragraph is the lead. |
|
|
86
|
+
| `focus` | `[{ path, note, startLine?, endLine?, repo? }]` | Where the user should look first, and why. `path` is relative to its repo or absolute. |
|
|
87
|
+
| `questions` | string[] | Only for a decision the agent can't make alone; usually left out. |
|
|
84
88
|
|
|
85
89
|
It blocks until the user submits (Claude Code keeps long MCP calls open; it sends progress notifications every 30 s for clients that need them) and returns markdown like:
|
|
86
90
|
|
|
@@ -97,11 +101,19 @@ Reviewed:
|
|
|
97
101
|
|
|
98
102
|
Almost there.
|
|
99
103
|
|
|
100
|
-
##
|
|
104
|
+
## Questions from the reviewer
|
|
101
105
|
|
|
102
|
-
|
|
106
|
+
The reviewer asks about this code. Answer each one in your reply. Change code only where a question shows something is wrong, and say so.
|
|
103
107
|
|
|
104
|
-
|
|
108
|
+
### /home/me/Projects/api
|
|
109
|
+
|
|
110
|
+
**src/config.ts:4** (new side)
|
|
111
|
+
|
|
112
|
+
```diff
|
|
113
|
+
+ host: env.HOST ?? "127.0.0.1",
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Why not 0.0.0.0 inside containers?
|
|
105
117
|
|
|
106
118
|
## Comments
|
|
107
119
|
|
|
@@ -161,7 +173,7 @@ Publishing to npm is done by GitHub Actions (`.github/workflows/publish.yml`) wi
|
|
|
161
173
|
2. Create a GitHub release with the tag `vX.Y.Z` matching that version: `gh release create v0.2.0 --generate-notes`.
|
|
162
174
|
3. The workflow checks the tag, runs the tests and publishes.
|
|
163
175
|
|
|
164
|
-
One-time setup: on npmjs.com, add a trusted publisher for the package (GitHub Actions, repository `
|
|
176
|
+
One-time setup: on npmjs.com, add a trusted publisher for the package (GitHub Actions, repository `polivkadev/feedbacker`, workflow `publish.yml`). npm only offers that once the package exists, so the very first version is published by hand with `npm publish --access public`.
|
|
165
177
|
|
|
166
178
|
Plans: [ROADMAP.md](ROADMAP.md).
|
|
167
179
|
|
package/bin/feedbacker.mjs
CHANGED
|
@@ -19,7 +19,8 @@ and prints the feedback to stdout when the user submits.
|
|
|
19
19
|
branch as base, the diff starts where HEAD forked from it.
|
|
20
20
|
-w, --worktrees Also review every other worktree of each repo.
|
|
21
21
|
-m, --summary TXT What the agent changed, shown at the top.
|
|
22
|
-
-
|
|
22
|
+
-f, --focus SPEC Where to look first: "path[:start[-end]]=why" (repeatable).
|
|
23
|
+
-q, --question TXT A decision you need from the user (repeatable; rarely).
|
|
23
24
|
--json Print JSON instead of markdown.
|
|
24
25
|
--port N Port to listen on (default: random).
|
|
25
26
|
--no-open Don't open the browser; only print the URL to stderr.
|
|
@@ -36,8 +37,16 @@ function fail(msg) {
|
|
|
36
37
|
process.exit(1);
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
// "src/core.mjs:120-160=Merge logic" -> { path, startLine, endLine, note }
|
|
41
|
+
function parseFocus(spec) {
|
|
42
|
+
const eq = spec.indexOf('=');
|
|
43
|
+
const where = eq < 0 ? spec : spec.slice(0, eq);
|
|
44
|
+
const m = where.match(/^(.*?)(?::(\d+)(?:-(\d+))?)?$/);
|
|
45
|
+
return { path: m[1], startLine: Number(m[2]) || 0, endLine: Number(m[3] || m[2]) || 0, note: eq < 0 ? '' : spec.slice(eq + 1) };
|
|
46
|
+
}
|
|
47
|
+
|
|
39
48
|
function parseArgs(argv) {
|
|
40
|
-
const opts = { repos: [], questions: [], summary: '', worktrees: false, json: false, port: 0, open: true };
|
|
49
|
+
const opts = { repos: [], questions: [], focus: [], summary: '', worktrees: false, json: false, port: 0, open: true };
|
|
41
50
|
for (let i = 0; i < argv.length; i++) {
|
|
42
51
|
const a = argv[i];
|
|
43
52
|
const next = () => {
|
|
@@ -47,6 +56,7 @@ function parseArgs(argv) {
|
|
|
47
56
|
if (a === '-h' || a === '--help') { console.log(USAGE); process.exit(0); }
|
|
48
57
|
else if (a === '-m' || a === '--summary') opts.summary = next();
|
|
49
58
|
else if (a === '-q' || a === '--question') opts.questions.push(next());
|
|
59
|
+
else if (a === '-f' || a === '--focus') opts.focus.push(parseFocus(next()));
|
|
50
60
|
else if (a === '-w' || a === '--worktrees') opts.worktrees = true;
|
|
51
61
|
else if (a === '--json') opts.json = true;
|
|
52
62
|
else if (a === '--port') opts.port = Number(next());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-feedbacker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "A local merge-request-style review page that coding agents open to collect your line comments and answers, across several git repos.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"repository": {
|
|
27
27
|
"type": "git",
|
|
28
|
-
"url": "git+https://github.com/
|
|
28
|
+
"url": "git+https://github.com/polivkadev/feedbacker.git"
|
|
29
29
|
},
|
|
30
30
|
"keywords": [
|
|
31
31
|
"claude-code",
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"esbuild"
|
|
50
50
|
]
|
|
51
51
|
},
|
|
52
|
-
"homepage": "https://github.com/
|
|
52
|
+
"homepage": "https://github.com/polivkadev/feedbacker#readme",
|
|
53
53
|
"bugs": {
|
|
54
|
-
"url": "https://github.com/
|
|
54
|
+
"url": "https://github.com/polivkadev/feedbacker/issues"
|
|
55
55
|
}
|
|
56
56
|
}
|
package/skills/review/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ description: Ask the user to review your changes in a local merge-request-style
|
|
|
5
5
|
|
|
6
6
|
# Review with Feedbacker
|
|
7
7
|
|
|
8
|
-
Feedbacker opens a review page in the user's browser with the diffs of the repos you pass. The user comments on lines,
|
|
8
|
+
Feedbacker opens a review page in the user's browser with the diffs of the repos you pass. The user comments on lines, asks you questions about the code, and clicks **Approve** or **Request changes**. You get their feedback back as markdown.
|
|
9
9
|
|
|
10
10
|
## How
|
|
11
11
|
|
|
@@ -13,8 +13,9 @@ Feedbacker opens a review page in the user's browser with the diffs of the repos
|
|
|
13
13
|
2. Pick a base per repo, usually by leaving it out: the plugin's hooks recorded where each repo stood when this session first touched it, and fill that in, so the review shows exactly what you changed in this session, commits included. Only pass `base` for something else, e.g. a whole branch against `main`. The result starts with a "Reviewed" list showing what the user saw.
|
|
14
14
|
3. Call the `request_review` tool of the `feedbacker` MCP server with:
|
|
15
15
|
- `repos`: `[{ "path": "/abs/path", "base": "main", "worktrees": false }, ...]`
|
|
16
|
-
- `summary`:
|
|
17
|
-
- `
|
|
16
|
+
- `summary`: markdown. One or two sentences on what changed and why, then a short `- ` list of the notable parts. Use `### ` subheadings only if the change has distinct parts. No walls of text.
|
|
17
|
+
- `focus`: two to six places the user should look first, each `{ "path": "src/x.ts", "startLine": 40, "endLine": 80, "note": "why it matters" }`. Pick the main logic and anything risky or non-obvious, not tests, docs or config (the page collapses those anyway).
|
|
18
|
+
- `questions`: leave this out. Only add one if you are blocked on a decision you truly cannot make; the user asks *you* questions on the page instead.
|
|
18
19
|
4. Before the call, tell the user a review page is opening in their browser. If it doesn't appear, they can run `node "${CLAUDE_PLUGIN_ROOT}/bin/feedbacker.mjs" list` to get its URL. The tool returns once they submit, however long that takes.
|
|
19
20
|
|
|
20
21
|
If the MCP tool isn't available, run the CLI in the background instead, so the shell timeout doesn't kill it, and read its output when it exits:
|
|
@@ -26,6 +27,7 @@ node "${CLAUDE_PLUGIN_ROOT}/bin/feedbacker.mjs" /abs/repo-a /abs/repo-b@main \
|
|
|
26
27
|
|
|
27
28
|
## After the review
|
|
28
29
|
|
|
30
|
+
- **Questions from the reviewer:** answer every one in your reply, quoting the file and line. Change code only if a question shows something is wrong, and say that you did.
|
|
29
31
|
- **Changes requested:** address every comment. Each one names the repo, file, line range and the code it refers to. Answer comments you disagree with instead of silently skipping them. Then offer another review round.
|
|
30
32
|
- **Approved:** carry on (commit, push, or whatever was next), taking any comments and answers into account.
|
|
31
33
|
- Use the answers to your questions as decisions; don't ask them again.
|
package/src/core.mjs
CHANGED
|
@@ -122,7 +122,74 @@ export function loadRepo(spec) {
|
|
|
122
122
|
|
|
123
123
|
// `worktrees` (globally or per repo) expands each repo into all its worktrees,
|
|
124
124
|
// e.g. to review parallel agents' work together. Duplicates are dropped.
|
|
125
|
-
|
|
125
|
+
// What kind of file a path is, for the page's quick filters. Works on paths
|
|
126
|
+
// only, for any language: tests by folder or file-name convention, docs and
|
|
127
|
+
// agent instructions (CLAUDE.md, AGENTS.md), config, and generated files
|
|
128
|
+
// (lock files, build output, minified, snapshots, binaries).
|
|
129
|
+
const TEST_DIRS = /(^|\/)(tests?|__tests__|__mocks__|specs?|testdata|test-data|fixtures|e2e|testing)\//i;
|
|
130
|
+
const TEST_FILES = [
|
|
131
|
+
/\.(test|spec|e2e|cy|stories|story)\.[^/]+$/i, // app.test.ts, app.spec.js, x.e2e.ts, x.cy.ts, x.stories.tsx
|
|
132
|
+
/(^|\/)test_[^/]+\.py$/i, /_test\.(py|go|rb|exs?|dart|cpp|cc|c|rs)$/i, // test_x.py, x_test.go
|
|
133
|
+
/(Tests?|Spec|IT)\.(java|kt|kts|scala|groovy|cs|fs|vb|swift|php)$/, // FooTest.java, FooTests.cs
|
|
134
|
+
/_spec\.rb$/i, /\.t$/, // foo_spec.rb, Perl foo.t
|
|
135
|
+
];
|
|
136
|
+
const GENERATED = [
|
|
137
|
+
/(^|\/)(package-lock\.json|npm-shrinkwrap\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb?|Cargo\.lock|go\.sum|poetry\.lock|uv\.lock|Pipfile\.lock|Gemfile\.lock|composer\.lock|mix\.lock|flake\.lock|Podfile\.lock|packages\.lock\.json)$/,
|
|
138
|
+
/\.min\.(js|css)$/i, /\.map$/i, /\.snap$/i, /\.lock$/i,
|
|
139
|
+
/(^|\/)(dist|build|out|vendor|node_modules|generated|__generated__|\.next|target)\//,
|
|
140
|
+
/\.(pb\.go|g\.dart|generated\.\w+|designer\.cs)$/i, /_pb2\.py$/,
|
|
141
|
+
];
|
|
142
|
+
const DOCS = [
|
|
143
|
+
/\.(md|mdx|markdown|rst|adoc|txt|org)$/i,
|
|
144
|
+
/(^|\/)(docs?|documentation)\//i,
|
|
145
|
+
/(^|\/)(LICEN[CS]E|COPYING|NOTICE|AUTHORS|CHANGELOG|CHANGES|CODEOWNERS)(\.\w+)?$/i,
|
|
146
|
+
];
|
|
147
|
+
const CONFIG = [
|
|
148
|
+
/\.(json|jsonc|json5|ya?ml|toml|ini|cfg|conf|properties|env|editorconfig|xml|plist)$/i,
|
|
149
|
+
/(^|\/)\.[^/]+$/, // dotfiles: .gitignore, .npmrc, .eslintrc
|
|
150
|
+
/(^|\/)\.(github|gitlab|circleci|vscode|idea|devcontainer|husky)\//,
|
|
151
|
+
/(^|\/)(Dockerfile|Containerfile|docker-compose[^/]*|Makefile|Justfile|Procfile|Brewfile)$/i,
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
export function classifyFile(file) {
|
|
155
|
+
const p = file.path || '';
|
|
156
|
+
if (file.binary) return 'generated';
|
|
157
|
+
if (GENERATED.some((re) => re.test(p))) return 'generated';
|
|
158
|
+
if (TEST_DIRS.test(p) || TEST_FILES.some((re) => re.test(p))) return 'test';
|
|
159
|
+
if (DOCS.some((re) => re.test(p))) return 'docs';
|
|
160
|
+
if (CONFIG.some((re) => re.test(p))) return 'config';
|
|
161
|
+
return 'code';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// The agent's pointers to what matters: [{ path, repo?, startLine?, endLine?,
|
|
165
|
+
// note }]. `path` is relative to its repo or absolute. Each match is attached
|
|
166
|
+
// to its file; review.focus keeps them in the agent's order for the page.
|
|
167
|
+
function attachFocus(repos, focus) {
|
|
168
|
+
const out = [];
|
|
169
|
+
for (const raw of Array.isArray(focus) ? focus : []) {
|
|
170
|
+
const item = typeof raw === 'string' ? { path: raw } : raw || {};
|
|
171
|
+
const note = typeof item.note === 'string' ? item.note : '';
|
|
172
|
+
const want = String(item.path || '').replace(/^\.\//, '');
|
|
173
|
+
if (!want) continue;
|
|
174
|
+
const startLine = Number(item.startLine) || 0;
|
|
175
|
+
const endLine = Number(item.endLine) || startLine;
|
|
176
|
+
let hit = null;
|
|
177
|
+
repos.forEach((repo, ri) => {
|
|
178
|
+
if (hit || (item.repo && path.resolve(item.repo) !== repo.path && path.basename(repo.path) !== item.repo)) return;
|
|
179
|
+
const rel = path.isAbsolute(want) ? path.relative(repo.path, want) : want;
|
|
180
|
+
const fi = repo.files.findIndex((f) => f.path === rel || f.oldPath === rel);
|
|
181
|
+
if (fi >= 0) hit = { ri, fi };
|
|
182
|
+
});
|
|
183
|
+
// Matched entries show the repo-relative path, however the agent wrote it.
|
|
184
|
+
const shown = hit ? repos[hit.ri].files[hit.fi].path : want;
|
|
185
|
+
const entry = { path: shown, note, startLine, endLine, ...(hit || { ri: -1, fi: -1 }) };
|
|
186
|
+
if (hit) (repos[hit.ri].files[hit.fi].focus ||= []).push({ note, startLine, endLine });
|
|
187
|
+
out.push(entry);
|
|
188
|
+
}
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function buildReview({ repos = ['.'], summary = '', questions = [], focus = [], worktrees = false }) {
|
|
126
193
|
const specs = [];
|
|
127
194
|
for (const raw of repos) {
|
|
128
195
|
const spec = parseSpec(raw);
|
|
@@ -141,7 +208,8 @@ export function buildReview({ repos = ['.'], summary = '', questions = [], workt
|
|
|
141
208
|
? path.join(path.basename(path.dirname(r.path)), name(r))
|
|
142
209
|
: name(r);
|
|
143
210
|
}
|
|
144
|
-
|
|
211
|
+
for (const r of loaded) for (const f of r.files) f.kind = classifyFile(f);
|
|
212
|
+
return { summary, questions, repos: loaded, focus: attachFocus(loaded, focus) };
|
|
145
213
|
}
|
|
146
214
|
|
|
147
215
|
export function hasChanges(review) {
|
|
@@ -161,14 +229,11 @@ export function toMarkdown(review, fb) {
|
|
|
161
229
|
}
|
|
162
230
|
out.push('');
|
|
163
231
|
if (fb.general.trim()) out.push('## General', '', fb.general.trim(), '');
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
if (fb.comments.length) {
|
|
169
|
-
out.push('## Comments', '');
|
|
232
|
+
const questions = fb.comments.filter((c) => c.kind === 'question');
|
|
233
|
+
const comments = fb.comments.filter((c) => c.kind !== 'question');
|
|
234
|
+
const byRepo = (list) => {
|
|
170
235
|
for (const repo of review.repos) {
|
|
171
|
-
const cs =
|
|
236
|
+
const cs = list.filter((c) => c.repo === repo.path);
|
|
172
237
|
if (!cs.length) continue;
|
|
173
238
|
out.push(`### ${repo.path}`, '');
|
|
174
239
|
for (const c of cs) {
|
|
@@ -176,6 +241,19 @@ export function toMarkdown(review, fb) {
|
|
|
176
241
|
out.push(`**${c.file}:${lines}** (${c.side} side)`, '', '```diff', c.code, '```', '', c.body.trim(), '');
|
|
177
242
|
}
|
|
178
243
|
}
|
|
244
|
+
};
|
|
245
|
+
if (questions.length) {
|
|
246
|
+
out.push('## Questions from the reviewer', '',
|
|
247
|
+
'The reviewer asks about this code. Answer each one in your reply. Change code only where a question shows something is wrong, and say so.', '');
|
|
248
|
+
byRepo(questions);
|
|
249
|
+
}
|
|
250
|
+
if (comments.length) {
|
|
251
|
+
out.push('## Comments', '');
|
|
252
|
+
byRepo(comments);
|
|
253
|
+
}
|
|
254
|
+
if (fb.answers.length) {
|
|
255
|
+
out.push('## Answers to your questions', '');
|
|
256
|
+
for (const a of fb.answers) out.push(`**Q: ${a.question}**`, '', a.answer.trim() || '_(no answer)_', '');
|
|
179
257
|
}
|
|
180
258
|
if (!fb.comments.length && !fb.general.trim() && !fb.answers.some((a) => a.answer.trim())) {
|
|
181
259
|
out.push('_No comments._', '');
|
|
@@ -196,6 +274,7 @@ export function normalizeFeedback(review, fb) {
|
|
|
196
274
|
const comments = (Array.isArray(fb.comments) ? fb.comments : []).map((c) => ({
|
|
197
275
|
repo: str(c.repo), file: str(c.file), side: c.side === 'old' ? 'old' : 'new',
|
|
198
276
|
startLine: Number(c.startLine) || 0, endLine: Number(c.endLine) || 0, code: str(c.code), body: str(c.body),
|
|
277
|
+
kind: c.kind === 'question' ? 'question' : 'comment',
|
|
199
278
|
}));
|
|
200
279
|
return { verdict: fb.verdict === 'approve' ? 'approve' : 'request_changes', general: str(fb.general), answers, comments };
|
|
201
280
|
}
|
package/src/dist/app.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
@font-face{font-family:Atkinson Hyperlegible Next Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url("./fonts/atkinson-hyperlegible-next-latin-ext-wght-normal-2N3YVG5Z.woff2") format("woff2-variations");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Atkinson Hyperlegible Next Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url("./fonts/atkinson-hyperlegible-next-latin-wght-normal-VX3WN42K.woff2") format("woff2-variations");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:JetBrains Mono Variable;font-style:normal;font-display:swap;font-weight:100 800;src:url("./fonts/jetbrains-mono-cyrillic-ext-wght-normal-BUWC6ZS3.woff2") format("woff2-variations");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:JetBrains Mono Variable;font-style:normal;font-display:swap;font-weight:100 800;src:url("./fonts/jetbrains-mono-cyrillic-wght-normal-ENSFVLK4.woff2") format("woff2-variations");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:JetBrains Mono Variable;font-style:normal;font-display:swap;font-weight:100 800;src:url("./fonts/jetbrains-mono-greek-wght-normal-Y3BRZMJV.woff2") format("woff2-variations");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:JetBrains Mono Variable;font-style:normal;font-display:swap;font-weight:100 800;src:url("./fonts/jetbrains-mono-vietnamese-wght-normal-SP6EBKAH.woff2") format("woff2-variations");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:JetBrains Mono Variable;font-style:normal;font-display:swap;font-weight:100 800;src:url("./fonts/jetbrains-mono-latin-ext-wght-normal-TSLVDSQR.woff2") format("woff2-variations");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:JetBrains Mono Variable;font-style:normal;font-display:swap;font-weight:100 800;src:url("./fonts/jetbrains-mono-latin-wght-normal-VBS632QH.woff2") format("woff2-variations");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}:root{--paper: #fbfbfc;--panel: #f2f3f6;--sunk: #e9ebf0;--ink: #1b1f2a;--muted: #5d6475;--faint: #8a90a0;--line: #dde0e7;--add: #e4f4ee;--add-gutter: #c8e9dc;--add-ink: #13795b;--del: #fbeaed;--del-gutter: #f4d1d8;--del-ink: #b4233c;--add-word: #a9dfc8;--del-word: #f0b6c1;--hunk: #eef0f6;--marker: #ffe766;--marker-soft: #fff6bf;--marker-ink: #5c4700;--logo-ink: #1b1f2a;--on-marker: #3d2f00;--focus: #3355d6;--approve: #13795b;--approve-ink: #ffffff;--reject: #b4233c;--reject-ink: #ffffff;--syn-keyword: #8a2a9c;--syn-string: #1c6b4a;--syn-number: #a5500a;--syn-comment: #7c8293;--syn-title: #2347b8;--syn-type: #9a3412;--syn-attr: #6d4c00;--sans: "Atkinson Hyperlegible Next Variable", system-ui, sans-serif;--mono: "JetBrains Mono Variable", ui-monospace, monospace;--bar: 56px;color-scheme:light}@media(prefers-color-scheme:dark){:root{--paper: #14171e;--panel: #1a1e27;--sunk: #222733;--ink: #e4e7ee;--muted: #9aa1b2;--faint: #6f7689;--line: #2a303c;--add: #10261f;--add-gutter: #173a2f;--add-ink: #5cc9a0;--del: #2a1820;--del-gutter: #45212d;--del-ink: #f0778b;--add-word: #1f5a45;--del-word: #6a2a3b;--hunk: #1c2130;--marker: #f2d44c;--marker-soft: rgba(242, 212, 76, .16);--marker-ink: #f2d44c;--focus: #7d97ff;--approve: #2f9e78;--reject: #c53a52;--syn-keyword: #d49bf0;--syn-string: #8fd3a8;--syn-number: #f0b072;--syn-comment: #737a8d;--syn-title: #8fb0ff;--syn-type: #f2a37f;--syn-attr: #e6c977;color-scheme:dark}}*{box-sizing:border-box}html{scroll-padding-top:calc(var(--bar) + 12px)}body{margin:0;background:var(--paper);color:var(--ink);font:15px/1.55 var(--sans);-webkit-font-smoothing:antialiased}button,textarea{font:inherit;color:inherit}:focus-visible{outline:2px solid var(--focus);outline-offset:2px}.bar{position:sticky;top:0;z-index:10;height:var(--bar);display:flex;align-items:center;gap:16px;padding:0 20px;background:color-mix(in srgb,var(--paper) 88%,transparent);backdrop-filter:blur(8px);border-bottom:1px solid var(--line)}.brand{display:inline-flex;align-items:center;gap:8px;white-space:nowrap}.brand .logo{flex:none;display:block}.brand .wordmark{font-weight:800;font-size:17px;letter-spacing:-.015em}.status .brand{margin-bottom:28px}.bar .stats{display:flex;gap:12px;color:var(--muted);font-size:14px;white-space:nowrap;overflow:hidden}.bar .spacer{flex:1}.plus{color:var(--add-ink);font-family:var(--mono);font-size:13px}.minus{color:var(--del-ink);font-family:var(--mono);font-size:13px}.marks{background:var(--marker);color:var(--on-marker);border-radius:999px;padding:0 9px;font-weight:700;font-size:13px;white-space:nowrap}.btn{border:1px solid var(--line);background:var(--panel);border-radius:8px;padding:6px 14px;cursor:pointer;font-weight:600;font-size:14px;white-space:nowrap}.btn:hover{background:var(--sunk)}.btn.approve{background:var(--approve);border-color:var(--approve);color:var(--approve-ink)}.btn.reject{background:transparent;border-color:var(--reject);color:var(--reject)}.btn.approve:hover{filter:brightness(1.08)}.btn.reject:hover{background:var(--del)}.btn.small{padding:3px 10px;font-size:13px;border-radius:6px}.btn.primary{background:var(--ink);border-color:var(--ink);color:var(--paper)}.btn.quiet{border-color:transparent;background:transparent;color:var(--muted)}.btn.quiet:hover{color:var(--ink);background:var(--sunk)}.picker{position:relative;display:inline-flex;min-width:0}.picker select{appearance:none;min-width:0;max-width:320px;text-overflow:ellipsis;font:inherit;font-size:14px;font-weight:600;color:var(--ink);background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:5px 30px 5px 12px;cursor:pointer}.picker select:hover{background:var(--sunk)}.picker:after{content:"\25be";position:absolute;right:11px;top:50%;transform:translateY(-52%);pointer-events:none;color:var(--muted);font-size:12px}.layout{display:grid;grid-template-columns:280px minmax(0,1fr)}.tree{position:sticky;top:var(--bar);align-self:start;height:calc(100vh - var(--bar));overflow:auto;padding:20px 12px 40px 16px;border-right:1px solid var(--line);font-size:14px}.tree .repo{display:flex;align-items:baseline;gap:8px;margin:0 0 6px 6px;font-weight:700}.tree>div+div{margin-top:22px}.branch{font-family:var(--mono);font-size:12px;font-weight:500;color:var(--muted);background:var(--sunk);border-radius:5px;padding:1px 6px;white-space:nowrap}.tree a{display:grid;grid-template-columns:14px minmax(0,1fr) auto;gap:8px;align-items:baseline;padding:3px 6px;border-radius:6px;color:var(--ink);text-decoration:none}.tree a:hover{background:var(--panel)}.tree .path{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;direction:rtl;text-align:left}.tree .count{font-size:12px}.tree .count .marks{padding:0 6px;font-size:12px}.st{font-family:var(--mono);font-size:12px;font-weight:700;color:var(--faint)}.st.added{color:var(--add-ink)}.st.deleted{color:var(--del-ink)}.st.renamed{color:var(--syn-title)}.review{padding:28px 32px 120px;min-width:0}.handoff{max-width:76ch;margin-bottom:36px}.handoff .from{color:var(--muted);font-size:14px;margin:0 0 6px}.summary{font-size:21px;line-height:1.5;margin:0;white-space:pre-wrap;text-wrap:pretty}.summary.empty{color:var(--muted);font-size:17px}.questions{margin-top:24px;display:grid;gap:14px}.question{border-left:3px solid var(--line);padding:2px 0 2px 16px}.question.answered{border-left-color:var(--approve)}.question p{margin:0 0 8px;font-weight:700;white-space:pre-wrap}textarea{display:block;width:100%;min-height:64px;resize:vertical;padding:8px 10px;border:1px solid var(--line);border-radius:8px;background:var(--paper);line-height:1.5}textarea:focus{border-color:var(--focus);outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--focus) 22%,transparent)}.repo-head{display:flex;align-items:baseline;flex-wrap:wrap;gap:10px;margin:32px 0 12px}.repo-head h2{margin:0;font-size:19px}.repo-head .where{color:var(--muted);font-size:13px;font-family:var(--mono);overflow-wrap:anywhere}.empty-note{color:var(--muted);margin:0 0 8px}.error{color:var(--del-ink)}.file{border:1px solid var(--line);border-radius:10px;margin-bottom:18px;background:var(--paper)}.fhead{position:sticky;top:var(--bar);z-index:5;display:flex;align-items:center;gap:10px;padding:8px 12px;background:var(--panel);border-bottom:1px solid var(--line);border-radius:10px 10px 0 0;font-family:var(--mono);font-size:13px}.file.collapsed .fhead{border-bottom:none;border-radius:10px}.fhead .name{flex:1;min-width:0;overflow-wrap:anywhere}.fhead .old{color:var(--muted)}.viewed-box{display:inline-flex;align-items:center;gap:6px;font:13px var(--sans);color:var(--muted);cursor:pointer;white-space:nowrap}.viewed-box input{accent-color:var(--approve);margin:0}.file.viewed .fhead .name{color:var(--muted)}.tree a.viewed .path{color:var(--faint)}.tree .tick{color:var(--approve);font-weight:700}.fold{border:none;background:none;cursor:pointer;color:var(--muted);padding:0 2px;font-size:13px;width:18px}.fold:hover{color:var(--ink)}.fnote{padding:12px 16px;color:var(--muted);font-size:14px}.scroll{overflow-x:auto;border-radius:0 0 10px 10px}table{border-collapse:collapse;width:100%;font:12.5px/21px var(--mono);font-variant-ligatures:none}td.ln{width:1%;min-width:48px;padding:0 10px;text-align:right;color:var(--faint);user-select:none;cursor:pointer;vertical-align:top;position:relative}tr:not(.hunk):hover td.ln:nth-child(2):before{content:"+";position:absolute;left:-2px;top:1px;width:18px;height:18px;line-height:18px;text-align:center;background:var(--marker);color:var(--on-marker);border-radius:5px;font-weight:700}td.ln:hover{color:var(--ink)}td.code{white-space:pre;padding:0 12px 0 4px}td.code .sign{display:inline-block;width:1.4em;color:var(--faint);user-select:none}tr.add td{background:var(--add)}tr.add td.ln{background:var(--add-gutter)}tr.add .sign{color:var(--add-ink)}tr.del td{background:var(--del)}tr.del td.ln{background:var(--del-gutter)}tr.del .sign{color:var(--del-ink)}mark.wd{color:inherit;padding:0;border-radius:0}tr.add mark.wd{background:var(--add-word)}tr.del mark.wd{background:var(--del-word)}tr.sel mark.wd{background:var(--marker);color:var(--on-marker, #3d2f00)}tr.hunk td{background:var(--hunk);color:var(--muted);padding-top:3px;padding-bottom:3px}tr.sel td{background:var(--marker-soft)!important}tr.sel td.ln{color:var(--ink)}tr.sel td.ln:first-child{box-shadow:inset 3px 0 0 var(--marker)}tr.comment>td{padding:8px 14px 10px 62px;background:var(--panel);font:15px/1.55 var(--sans);white-space:normal;border-top:1px solid var(--line);border-bottom:1px solid var(--line)}.note-card{border-left:3px solid var(--marker);padding:4px 0 4px 12px;max-width:80ch}.note-card .meta{display:flex;align-items:center;gap:8px;color:var(--muted);font-size:13px;margin-bottom:2px}.note-card .meta .where{font-family:var(--mono);font-size:12px}.note-card .meta .actions{margin-left:auto;display:flex;gap:2px}.note-card p{margin:0;white-space:pre-wrap}.form{max-width:80ch}.form .row{display:flex;align-items:center;gap:8px;margin-top:8px}.form .hint{color:var(--faint);font-size:13px;margin-right:auto}.finish{max-width:76ch;margin-top:44px;padding-top:24px;border-top:1px solid var(--line)}.finish h2{margin:0 0 4px;font-size:19px}.finish p{margin:0 0 12px;color:var(--muted)}.finish .row{display:flex;gap:10px;margin-top:12px;flex-wrap:wrap}.hljs-keyword,.hljs-selector-tag,.hljs-built_in,.hljs-literal{color:var(--syn-keyword)}.hljs-string,.hljs-regexp,.hljs-template-tag,.hljs-addition{color:var(--syn-string)}.hljs-number,.hljs-symbol,.hljs-variable.constant_{color:var(--syn-number)}.hljs-comment,.hljs-quote,.hljs-meta{color:var(--syn-comment);font-style:italic}.hljs-title,.hljs-title.function_,.hljs-section,.hljs-name{color:var(--syn-title)}.hljs-type,.hljs-title.class_,.hljs-selector-class{color:var(--syn-type)}.hljs-attr,.hljs-attribute,.hljs-property,.hljs-params{color:var(--syn-attr)}.hljs-strong{font-weight:700}.hljs-emphasis{font-style:italic}.status{max-width:46ch;margin:22vh auto 0;padding:0 16px}.status h1{font-size:30px;margin:0 0 8px}.status p{color:var(--muted);margin:0;font-size:17px}.status .stamp{display:table;margin-bottom:18px;padding:2px 12px;border-radius:999px;font-weight:700;font-size:14px;animation:stamp .42s cubic-bezier(.2,.9,.3,1.3)}.status .stamp.approve{background:var(--add);color:var(--add-ink)}.status .stamp.request_changes{background:var(--del);color:var(--del-ink)}@keyframes stamp{0%{transform:scale(.6);opacity:0}to{transform:scale(1);opacity:1}}@media(prefers-reduced-motion:reduce){.status .stamp{animation:none}}@media(max-width:900px){.layout{display:block}.tree{display:none}.review{padding:20px 16px 100px}.bar{padding:0 16px;gap:10px}.bar .stats,.bar .marks,.bar .wordmark{display:none}.picker select{max-width:34vw}.bar .btn{padding:6px 10px}.summary{font-size:18px}tr.comment>td{padding-left:14px}}
|
|
1
|
+
@font-face{font-family:Atkinson Hyperlegible Next Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url("./fonts/atkinson-hyperlegible-next-latin-ext-wght-normal-2N3YVG5Z.woff2") format("woff2-variations");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Atkinson Hyperlegible Next Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url("./fonts/atkinson-hyperlegible-next-latin-wght-normal-VX3WN42K.woff2") format("woff2-variations");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:JetBrains Mono Variable;font-style:normal;font-display:swap;font-weight:100 800;src:url("./fonts/jetbrains-mono-cyrillic-ext-wght-normal-BUWC6ZS3.woff2") format("woff2-variations");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:JetBrains Mono Variable;font-style:normal;font-display:swap;font-weight:100 800;src:url("./fonts/jetbrains-mono-cyrillic-wght-normal-ENSFVLK4.woff2") format("woff2-variations");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:JetBrains Mono Variable;font-style:normal;font-display:swap;font-weight:100 800;src:url("./fonts/jetbrains-mono-greek-wght-normal-Y3BRZMJV.woff2") format("woff2-variations");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:JetBrains Mono Variable;font-style:normal;font-display:swap;font-weight:100 800;src:url("./fonts/jetbrains-mono-vietnamese-wght-normal-SP6EBKAH.woff2") format("woff2-variations");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:JetBrains Mono Variable;font-style:normal;font-display:swap;font-weight:100 800;src:url("./fonts/jetbrains-mono-latin-ext-wght-normal-TSLVDSQR.woff2") format("woff2-variations");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:JetBrains Mono Variable;font-style:normal;font-display:swap;font-weight:100 800;src:url("./fonts/jetbrains-mono-latin-wght-normal-VBS632QH.woff2") format("woff2-variations");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}:root{--paper: #fbfbfc;--panel: #f2f3f6;--sunk: #e9ebf0;--ink: #1b1f2a;--muted: #5d6475;--faint: #8a90a0;--line: #dde0e7;--add: #e4f4ee;--add-gutter: #c8e9dc;--add-ink: #13795b;--del: #fbeaed;--del-gutter: #f4d1d8;--del-ink: #b4233c;--add-word: #a9dfc8;--del-word: #f0b6c1;--hunk: #eef0f6;--marker: #ffe766;--marker-soft: #fff6bf;--marker-ink: #5c4700;--logo-ink: #1b1f2a;--on-marker: #3d2f00;--focus: #3355d6;--agent: #3355d6;--agent-soft: #eaeffd;--approve: #13795b;--approve-ink: #ffffff;--reject: #b4233c;--reject-ink: #ffffff;--syn-keyword: #8a2a9c;--syn-string: #1c6b4a;--syn-number: #a5500a;--syn-comment: #7c8293;--syn-title: #2347b8;--syn-type: #9a3412;--syn-attr: #6d4c00;--sans: "Atkinson Hyperlegible Next Variable", system-ui, sans-serif;--mono: "JetBrains Mono Variable", ui-monospace, monospace;--bar: 56px;color-scheme:light}@media(prefers-color-scheme:dark){:root{--paper: #14171e;--panel: #1a1e27;--sunk: #222733;--ink: #e4e7ee;--muted: #9aa1b2;--faint: #6f7689;--line: #2a303c;--add: #10261f;--add-gutter: #173a2f;--add-ink: #5cc9a0;--del: #2a1820;--del-gutter: #45212d;--del-ink: #f0778b;--add-word: #1f5a45;--del-word: #6a2a3b;--hunk: #1c2130;--marker: #f2d44c;--marker-soft: rgba(242, 212, 76, .16);--marker-ink: #f2d44c;--focus: #7d97ff;--agent: #8fa6ff;--agent-soft: #1b2236;--approve: #2f9e78;--reject: #c53a52;--syn-keyword: #d49bf0;--syn-string: #8fd3a8;--syn-number: #f0b072;--syn-comment: #737a8d;--syn-title: #8fb0ff;--syn-type: #f2a37f;--syn-attr: #e6c977;color-scheme:dark}}*{box-sizing:border-box}html{scroll-padding-top:calc(var(--bar) + 12px)}body{margin:0;background:var(--paper);color:var(--ink);font:15px/1.55 var(--sans);-webkit-font-smoothing:antialiased}button,textarea{font:inherit;color:inherit}:focus-visible{outline:2px solid var(--focus);outline-offset:2px}.bar{position:sticky;top:0;z-index:10;height:var(--bar);display:flex;align-items:center;gap:16px;padding:0 20px;background:color-mix(in srgb,var(--paper) 88%,transparent);backdrop-filter:blur(8px);border-bottom:1px solid var(--line)}.brand{display:inline-flex;align-items:center;gap:8px;white-space:nowrap}.brand .logo{flex:none;display:block}.brand .wordmark{font-weight:800;font-size:17px;letter-spacing:-.015em}.status .brand{margin-bottom:28px}.bar .stats{display:flex;gap:12px;color:var(--muted);font-size:14px;white-space:nowrap;overflow:hidden}.bar .spacer{flex:1}.plus{color:var(--add-ink);font-family:var(--mono);font-size:13px}.minus{color:var(--del-ink);font-family:var(--mono);font-size:13px}.marks{background:var(--marker);color:var(--on-marker);border-radius:999px;padding:0 9px;font-weight:700;font-size:13px;white-space:nowrap}.btn{border:1px solid var(--line);background:var(--panel);border-radius:8px;padding:6px 14px;cursor:pointer;font-weight:600;font-size:14px;white-space:nowrap}.btn:hover{background:var(--sunk)}.btn.approve{background:var(--approve);border-color:var(--approve);color:var(--approve-ink)}.btn.reject{background:transparent;border-color:var(--reject);color:var(--reject)}.btn.approve:hover{filter:brightness(1.08)}.btn.reject:hover{background:var(--del)}.btn.small{padding:3px 10px;font-size:13px;border-radius:6px}.btn.primary{background:var(--ink);border-color:var(--ink);color:var(--paper)}.btn.quiet{border-color:transparent;background:transparent;color:var(--muted)}.btn.quiet:hover{color:var(--ink);background:var(--sunk)}.picker{position:relative;display:inline-flex;min-width:0}.picker select{appearance:none;min-width:0;max-width:320px;text-overflow:ellipsis;font:inherit;font-size:14px;font-weight:600;color:var(--ink);background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:5px 30px 5px 12px;cursor:pointer}.picker select:hover{background:var(--sunk)}.picker:after{content:"\25be";position:absolute;right:11px;top:50%;transform:translateY(-52%);pointer-events:none;color:var(--muted);font-size:12px}.layout{display:grid;grid-template-columns:280px minmax(0,1fr)}.tree{position:sticky;top:var(--bar);align-self:start;height:calc(100vh - var(--bar));overflow:auto;padding:20px 12px 40px 16px;border-right:1px solid var(--line);font-size:14px}.tree .repo{display:flex;align-items:baseline;gap:8px;margin:0 0 6px 6px;font-weight:700}.tree>div+div{margin-top:22px}.branch{font-family:var(--mono);font-size:12px;font-weight:500;color:var(--muted);background:var(--sunk);border-radius:5px;padding:1px 6px;white-space:nowrap}.tree a{display:grid;grid-template-columns:14px minmax(0,1fr) auto;gap:8px;align-items:baseline;padding:3px 6px;border-radius:6px;color:var(--ink);text-decoration:none}.tree a:hover{background:var(--panel)}.tree a.minor .path{color:var(--muted)}.tree a.focused .path{font-weight:700;color:var(--ink)}.tree .path{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;direction:rtl;text-align:left}.tree .count{font-size:12px}.tree .count .marks{padding:0 6px;font-size:12px}.st{font-family:var(--mono);font-size:12px;font-weight:700;color:var(--faint)}.st.added{color:var(--add-ink)}.st.deleted{color:var(--del-ink)}.st.renamed{color:var(--syn-title)}.review{padding:28px 32px 120px;min-width:0}.handoff{max-width:76ch;margin-bottom:28px}.summary{font-size:15.5px;line-height:1.6;text-wrap:pretty}.summary>:first-child{margin-top:0}.summary p{margin:0 0 10px}.summary .summary-lead,.summary-lead{font-size:19px;line-height:1.5;margin:0 0 12px}.summary-lead.empty{color:var(--muted);font-size:17px}.summary h3{font-size:15px;margin:18px 0 6px}.summary ul,.summary ol{margin:0 0 12px;padding-left:22px}.summary li{margin:3px 0}.summary code,.focus-list a,.focus-list .missing{font-family:var(--mono);font-size:.88em}.summary code{background:var(--sunk);padding:1px 5px;border-radius:4px}.handoff h3{font-size:15px;margin:0 0 8px}.focus-list{margin-top:22px;padding:12px 16px;border-left:3px solid var(--agent);background:var(--agent-soft);border-radius:0 8px 8px 0}.focus-list ul{list-style:none;margin:0;padding:0;display:grid;gap:10px}.focus-list li{display:grid;gap:1px}.focus-list a{color:var(--agent);text-decoration:none;font-weight:600}.focus-list a:hover{text-decoration:underline}.focus-list a .repo{font-family:var(--sans);font-weight:700;color:var(--muted);margin-right:6px}.focus-list .missing{color:var(--muted);text-decoration:line-through}.focus-list .why{color:var(--ink)}.questions{margin-top:22px;display:grid;gap:12px}.question{border-left:3px solid var(--line);padding:2px 0 2px 16px}.question.answered{border-left-color:var(--approve)}.question p{margin:0 0 8px;font-weight:700;white-space:pre-wrap}.toolbar{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:10px 20px;margin-bottom:4px}.toolbar .empty-note{margin:0}.filters{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.filters .lead{color:var(--muted);font-size:13px;margin-right:2px}.chip{font:inherit;font-size:13px;font-weight:600;cursor:pointer;border:1px solid var(--line);background:transparent;color:var(--muted);border-radius:999px;padding:2px 10px}.chip .n{font-weight:500;margin-left:2px}.chip:hover{background:var(--panel);color:var(--ink)}.chip.on{background:var(--ink);border-color:var(--ink);color:var(--paper)}textarea{display:block;width:100%;min-height:64px;resize:vertical;padding:8px 10px;border:1px solid var(--line);border-radius:8px;background:var(--paper);line-height:1.5}textarea:focus{border-color:var(--focus);outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--focus) 22%,transparent)}.repo-head{display:flex;align-items:baseline;flex-wrap:wrap;gap:10px;margin:32px 0 12px}.repo-head h2{margin:0;font-size:19px}.repo-head .where{color:var(--muted);font-size:13px;font-family:var(--mono);overflow-wrap:anywhere}.empty-note{color:var(--muted);margin:0 0 8px}.error{color:var(--del-ink)}.file{border:1px solid var(--line);border-radius:10px;margin-bottom:18px;background:var(--paper)}.fhead{position:sticky;top:var(--bar);z-index:5;display:flex;align-items:center;gap:10px;padding:8px 12px;background:var(--panel);border-bottom:1px solid var(--line);border-radius:10px 10px 0 0;font-family:var(--mono);font-size:13px}.file.collapsed .fhead{border-bottom:none;border-radius:10px}.fhead .name{flex:1;min-width:0;overflow-wrap:anywhere}.fhead .old{color:var(--muted)}.kind-tag{font:12px var(--sans);color:var(--muted);background:var(--sunk);border-radius:5px;padding:0 6px}.file.focused{border-color:color-mix(in srgb,var(--agent) 45%,var(--line))}tr.agent-note>td{padding:6px 14px 6px 62px;background:var(--agent-soft);font:14px/1.5 var(--sans);white-space:normal}.agent-note.file-level{padding:8px 14px;background:var(--agent-soft);border-bottom:1px solid var(--line);font-size:14px}.agent-card{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 10px;border-left:3px solid var(--agent);padding-left:10px;max-width:90ch}.agent-card .who{color:var(--agent);font-weight:700;font-size:13px}.agent-card .lines{color:var(--muted);font-size:12px;font-family:var(--mono)}.viewed-box{display:inline-flex;align-items:center;gap:6px;font:13px var(--sans);color:var(--muted);cursor:pointer;white-space:nowrap}.viewed-box input{accent-color:var(--approve);margin:0}.file.viewed .fhead .name{color:var(--muted)}.tree a.viewed .path{color:var(--faint)}.tree .tick{color:var(--approve);font-weight:700}.fold{border:none;background:none;cursor:pointer;color:var(--muted);padding:0 2px;font-size:13px;width:18px}.fold:hover{color:var(--ink)}.fnote{padding:12px 16px;color:var(--muted);font-size:14px}.scroll{overflow-x:auto;border-radius:0 0 10px 10px}table{border-collapse:collapse;width:100%;font:12.5px/21px var(--mono);font-variant-ligatures:none}td.ln{width:1%;min-width:48px;padding:0 10px;text-align:right;color:var(--faint);user-select:none;cursor:pointer;vertical-align:top;position:relative}tr:not(.hunk):hover td.ln:nth-child(2):before{content:"+";position:absolute;left:-2px;top:1px;width:18px;height:18px;line-height:18px;text-align:center;background:var(--marker);color:var(--on-marker);border-radius:5px;font-weight:700}td.ln:hover{color:var(--ink)}td.code{white-space:pre;padding:0 12px 0 4px}td.code .sign{display:inline-block;width:1.4em;color:var(--faint);user-select:none}tr.add td{background:var(--add)}tr.add td.ln{background:var(--add-gutter)}tr.add .sign{color:var(--add-ink)}tr.del td{background:var(--del)}tr.del td.ln{background:var(--del-gutter)}tr.del .sign{color:var(--del-ink)}mark.wd{color:inherit;padding:0;border-radius:0}tr.add mark.wd{background:var(--add-word)}tr.del mark.wd{background:var(--del-word)}tr.sel mark.wd{background:var(--marker);color:var(--on-marker, #3d2f00)}tr.hunk td{background:var(--hunk);color:var(--muted);padding-top:3px;padding-bottom:3px}tr.sel td{background:var(--marker-soft)!important}tr.sel td.ln{color:var(--ink)}tr.sel td.ln:first-child{box-shadow:inset 3px 0 0 var(--marker)}tr.comment>td{padding:8px 14px 10px 62px;background:var(--panel);font:15px/1.55 var(--sans);white-space:normal;border-top:1px solid var(--line);border-bottom:1px solid var(--line)}.note-card{border-left:3px solid var(--marker);padding:4px 0 4px 12px;max-width:80ch}.note-card .meta{display:flex;align-items:center;gap:8px;color:var(--muted);font-size:13px;margin-bottom:2px}.note-card .meta .where{font-family:var(--mono);font-size:12px}.note-card .meta .actions{margin-left:auto;display:flex;gap:2px}.note-card p{margin:0;white-space:pre-wrap}.note-card.question-card{border-left-color:var(--agent)}.kind-pill{color:var(--agent);font-weight:700;font-size:12px}.marks.ask{background:var(--agent-soft);color:var(--agent)}.btn.ask{border-color:var(--agent);color:var(--agent);background:transparent}.btn.ask:hover{background:var(--agent-soft)}.form{max-width:80ch}.form .row{display:flex;align-items:center;gap:8px;margin-top:8px}.form .hint{color:var(--faint);font-size:13px;margin-right:auto}.finish{max-width:76ch;margin-top:44px;padding-top:24px;border-top:1px solid var(--line)}.finish h2{margin:0 0 4px;font-size:19px}.finish p{margin:0 0 12px;color:var(--muted)}.finish .row{display:flex;gap:10px;margin-top:12px;flex-wrap:wrap}.hljs-keyword,.hljs-selector-tag,.hljs-built_in,.hljs-literal{color:var(--syn-keyword)}.hljs-string,.hljs-regexp,.hljs-template-tag,.hljs-addition{color:var(--syn-string)}.hljs-number,.hljs-symbol,.hljs-variable.constant_{color:var(--syn-number)}.hljs-comment,.hljs-quote,.hljs-meta{color:var(--syn-comment);font-style:italic}.hljs-title,.hljs-title.function_,.hljs-section,.hljs-name{color:var(--syn-title)}.hljs-type,.hljs-title.class_,.hljs-selector-class{color:var(--syn-type)}.hljs-attr,.hljs-attribute,.hljs-property,.hljs-params{color:var(--syn-attr)}.hljs-strong{font-weight:700}.hljs-emphasis{font-style:italic}.status{max-width:46ch;margin:22vh auto 0;padding:0 16px}.status h1{font-size:30px;margin:0 0 8px}.status p{color:var(--muted);margin:0;font-size:17px}.status .stamp{display:table;margin-bottom:18px;padding:2px 12px;border-radius:999px;font-weight:700;font-size:14px;animation:stamp .42s cubic-bezier(.2,.9,.3,1.3)}.status .stamp.approve{background:var(--add);color:var(--add-ink)}.status .stamp.request_changes{background:var(--del);color:var(--del-ink)}@keyframes stamp{0%{transform:scale(.6);opacity:0}to{transform:scale(1);opacity:1}}@media(prefers-reduced-motion:reduce){.status .stamp{animation:none}}@media(max-width:900px){.layout{display:block}.tree{display:none}.review{padding:20px 16px 100px}.bar{padding:0 16px;gap:10px}.bar .stats,.bar .marks,.bar .wordmark{display:none}.picker select{max-width:34vw}.bar .btn{padding:6px 10px}.summary{font-size:18px}tr.comment>td{padding-left:14px}}
|