@xbghc/warden 0.13.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -11,6 +11,11 @@ with line comments you can copy back to the agent as a prompt.
|
|
|
11
11
|
releasing one keeps the directory, installed dependencies included, for the next branch.
|
|
12
12
|
- Side-by-side **Unstaged** and **Staged** file lists: staged means reviewed. Stage from the UI by dragging
|
|
13
13
|
over the lines you have read (or a hunk, or a file), and unstage the same way from the Staged view.
|
|
14
|
+
- Debug code marked in a comment is folded out of the review and left out of whole-file staging, and
|
|
15
|
+
the Staged block warns while any of it is in the index (see [Debug code](#debug-code)).
|
|
16
|
+
- Code blocks fold by indentation, as an editor does for a language it has no grammar for: the
|
|
17
|
+
chevron beside a line that heads one shuts its body, and the head then says how many lines and
|
|
18
|
+
changes it hides. A dragged pick across a shut block takes none of them; stage the hunk to take them.
|
|
14
19
|
- GitHub-style unified / side-by-side diff with syntax highlighting, collapsed file tree, lazy per-file loading, context expansion, virtual scrolling.
|
|
15
20
|
- Line comments (single line or a dragged range), Markdown, edit / delete.
|
|
16
21
|
- One click copies all comments as an agent-readable prompt to the clipboard.
|
|
@@ -200,6 +205,34 @@ Limits, each reported as a plain error rather than a half-applied patch:
|
|
|
200
205
|
(the agent kept editing), the server answers 409 and the page reloads instead of staging the wrong lines.
|
|
201
206
|
- If another git process holds the index lock the server retries briefly, then gives up with 409.
|
|
202
207
|
|
|
208
|
+
## Debug code
|
|
209
|
+
|
|
210
|
+
Code written to poke at a problem and not meant to be committed can be marked in a comment. There is no
|
|
211
|
+
single convention for this, so warden takes the ones in use, in any comment syntax (`//`, `/*`, `#`,
|
|
212
|
+
`--`, `<!--`, `;`, `%`) and any case, as long as the marker opens the comment:
|
|
213
|
+
|
|
214
|
+
- a block between `debug:start` and `debug:end`, or `develblock:start` and `develblock:end` (the
|
|
215
|
+
webpack-strip-block / gulp-strip-block spelling); markers included, blocks nest, and a block left
|
|
216
|
+
open runs to the end of the file;
|
|
217
|
+
- a single line tagged `nocommit`, `no-commit` or `do not commit` (optionally `@nocommit` / `!nocommit`),
|
|
218
|
+
the tag the usual pre-commit hooks look for.
|
|
219
|
+
|
|
220
|
+
With *忽略调试代码* ticked in the sidebar (the default):
|
|
221
|
+
|
|
222
|
+
- runs of debug lines in a hunk fold into one striped row that opens with a click; jumping to a comment
|
|
223
|
+
inside one opens it;
|
|
224
|
+
- *暂存文件* and *暂存此 hunk* leave debug lines out. Lines picked one by one go in regardless — that
|
|
225
|
+
is how debug code is staged on purpose. A file whose changes are all debug code is refused with a
|
|
226
|
+
note to pick the lines instead;
|
|
227
|
+
- a file whose unstaged changes are all debug code does not count as still to review.
|
|
228
|
+
|
|
229
|
+
Whether it is ticked or not, the 已暂存 heading carries a *调试代码 N* mark while N added lines in the
|
|
230
|
+
index are debug code, with the files in its tooltip.
|
|
231
|
+
|
|
232
|
+
A hunk rarely shows the marker that opened the block its lines sit in, so warden reads both sides of a
|
|
233
|
+
file whole — only for the files `git grep` finds a marker in. Marker detection is textual: a marker
|
|
234
|
+
inside a string literal that follows a comment leader counts too.
|
|
235
|
+
|
|
203
236
|
## Comments and export
|
|
204
237
|
|
|
205
238
|
Press the `+` that appears next to a line (drag to cover several lines) and write Markdown. What you
|
|
@@ -360,7 +393,8 @@ first time the file is read, and the 已读 marks those views used to keep are d
|
|
|
360
393
|
|
|
361
394
|
Single user, local only. The server binds to `127.0.0.1` and executes git only through
|
|
362
395
|
`execFile('git', [...])`. Reads go through an argument whitelist (`rev-parse`, `diff`, `show`, `log`,
|
|
363
|
-
`worktree list`, `ls-files`, `status`, `merge-base`, `rev-list`
|
|
396
|
+
`worktree list`, `ls-files`, `status`, `merge-base`, `rev-list`, `grep` without `-O` /
|
|
397
|
+
`--open-files-in-pager`) that refuses option-looking refs and any write-capable
|
|
364
398
|
flag; requests that would need anything else get HTTP 400. The writes are few and each composes its
|
|
365
399
|
own arguments. `POST /api/targets/:key/stage` runs `git apply --cached` (with `--reverse` for the
|
|
366
400
|
Staged view) on a patch the server itself builds from the diff it just produced — the patch is never
|
package/dist/cli.js
CHANGED
|
@@ -3059,7 +3059,8 @@ var ALLOWED_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
|
3059
3059
|
"merge-base",
|
|
3060
3060
|
"rev-list",
|
|
3061
3061
|
"for-each-ref",
|
|
3062
|
-
"check-ref-format"
|
|
3062
|
+
"check-ref-format",
|
|
3063
|
+
"grep"
|
|
3063
3064
|
]);
|
|
3064
3065
|
var FORBIDDEN_OPTION_PREFIXES = ["--output", "--ext-diff", "--textconv", "-c", "--config-env", "--exec-path", "--git-dir", "--work-tree"];
|
|
3065
3066
|
var DEFAULT_GIT_TIMEOUT_MS = 3e4;
|
|
@@ -3082,6 +3083,15 @@ function assertAllowedGitArgs(args) {
|
|
|
3082
3083
|
if (sub === "worktree" && args[1] !== "list") {
|
|
3083
3084
|
throw new GitError(`git worktree ${args[1] ?? ""} is not allowed`, null, "", 400, "git_subcommand_forbidden");
|
|
3084
3085
|
}
|
|
3086
|
+
if (sub === "grep") {
|
|
3087
|
+
for (const arg of args.slice(1)) {
|
|
3088
|
+
if (arg === "--") break;
|
|
3089
|
+
const name = arg.split("=")[0];
|
|
3090
|
+
if (arg.startsWith("-O") || name.length > 3 && "--open-files-in-pager".startsWith(name)) {
|
|
3091
|
+
throw new GitError(`git option not allowed: ${arg}`, null, "", 400, "git_option_forbidden");
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3085
3095
|
for (const arg of args.slice(1)) {
|
|
3086
3096
|
if (arg.includes("\0")) throw new GitError("NUL byte in git argument", null, "", 400, "git_bad_argument");
|
|
3087
3097
|
for (const prefix of FORBIDDEN_OPTION_PREFIXES) {
|
|
@@ -3587,6 +3597,40 @@ function parseUnifiedDiff(text) {
|
|
|
3587
3597
|
return files;
|
|
3588
3598
|
}
|
|
3589
3599
|
|
|
3600
|
+
// packages/server/src/debug.ts
|
|
3601
|
+
var LEADER = String.raw`(?:\/\/+|\/\*+|#+|--|<!--|;+|%+)\s*`;
|
|
3602
|
+
var BLOCK_START = new RegExp(`${LEADER}(?:debug|develblock):start\\b`, "i");
|
|
3603
|
+
var BLOCK_END = new RegExp(`${LEADER}(?:debug|develblock):end\\b`, "i");
|
|
3604
|
+
var LINE_TAG = new RegExp(`${LEADER}[@!]?(?:no-?commit|do not commit)\\b`, "i");
|
|
3605
|
+
var DEBUG_MARKER_STRINGS = ["debug:start", "develblock:start", "nocommit", "no-commit", "do not commit"];
|
|
3606
|
+
function debugLineNumbers(text) {
|
|
3607
|
+
const out = /* @__PURE__ */ new Set();
|
|
3608
|
+
const lines = text.split("\n");
|
|
3609
|
+
let depth = 0;
|
|
3610
|
+
lines.forEach((line, i) => {
|
|
3611
|
+
const no = i + 1;
|
|
3612
|
+
if (BLOCK_START.test(line)) depth++;
|
|
3613
|
+
if (depth > 0 || LINE_TAG.test(line)) out.add(no);
|
|
3614
|
+
if (depth > 0 && BLOCK_END.test(line)) depth--;
|
|
3615
|
+
});
|
|
3616
|
+
return out;
|
|
3617
|
+
}
|
|
3618
|
+
function markDebugLines(diff, newSide, oldSide) {
|
|
3619
|
+
let added = 0;
|
|
3620
|
+
let deleted = 0;
|
|
3621
|
+
for (const hunk of diff.hunks) {
|
|
3622
|
+
for (const line of hunk.lines) {
|
|
3623
|
+
const debug = line.type === "del" ? !!oldSide?.has(line.oldLineNo) : !!newSide?.has(line.newLineNo);
|
|
3624
|
+
if (!debug) continue;
|
|
3625
|
+
line.debug = true;
|
|
3626
|
+
if (line.type === "add") added++;
|
|
3627
|
+
else if (line.type === "del") deleted++;
|
|
3628
|
+
}
|
|
3629
|
+
}
|
|
3630
|
+
if (added) diff.debugAdditions = added;
|
|
3631
|
+
if (deleted) diff.debugDeletions = deleted;
|
|
3632
|
+
}
|
|
3633
|
+
|
|
3590
3634
|
// packages/server/src/targets.ts
|
|
3591
3635
|
var DIFF_BASE_ARGS = ["diff", "--no-color", "--no-ext-diff", "-U3", "-M", "--find-renames"];
|
|
3592
3636
|
function resolveTargetContext(repo, worktrees, key) {
|
|
@@ -3722,7 +3766,8 @@ async function refForSide(ctx, side) {
|
|
|
3722
3766
|
if (side === "new") return t.sha;
|
|
3723
3767
|
return await revParse(ctx.cwd, `${t.sha}^`) ?? EMPTY_TREE_SHA;
|
|
3724
3768
|
case "range":
|
|
3725
|
-
|
|
3769
|
+
if (side === "new") return t.head;
|
|
3770
|
+
return await mergeBase(ctx.cwd, t.base, t.head) ?? t.base;
|
|
3726
3771
|
case "base":
|
|
3727
3772
|
return side === "new" ? void 0 : baseSha(ctx, t.ref);
|
|
3728
3773
|
}
|
|
@@ -3746,6 +3791,49 @@ async function getFullFile(ctx, filePath, side) {
|
|
|
3746
3791
|
throw e;
|
|
3747
3792
|
}
|
|
3748
3793
|
}
|
|
3794
|
+
var GREP_CHUNK = 500;
|
|
3795
|
+
async function pathsWithMarkers(ctx, side, paths) {
|
|
3796
|
+
const found = /* @__PURE__ */ new Set();
|
|
3797
|
+
if (paths.length === 0) return found;
|
|
3798
|
+
const ref = await refForSide(ctx, side);
|
|
3799
|
+
const args = ["grep", "-l", "-z", "-I", "-i", "-F", ...DEBUG_MARKER_STRINGS.flatMap((m) => ["-e", m])];
|
|
3800
|
+
if (ref === void 0) args.push("--untracked");
|
|
3801
|
+
else if (ref === ":0") args.push("--cached");
|
|
3802
|
+
else args.push(ref);
|
|
3803
|
+
const prefix = ref === void 0 || ref === ":0" ? "" : `${ref}:`;
|
|
3804
|
+
for (let i = 0; i < paths.length; i += GREP_CHUNK) {
|
|
3805
|
+
const chunk = paths.slice(i, i + GREP_CHUNK).map(literal);
|
|
3806
|
+
const r = await runGit([...args, "--", ...chunk], { cwd: ctx.cwd, okCodes: [0, 1] });
|
|
3807
|
+
for (const p of r.stdout.split("\0")) if (p) found.add(p.startsWith(prefix) ? p.slice(prefix.length) : p);
|
|
3808
|
+
}
|
|
3809
|
+
return found;
|
|
3810
|
+
}
|
|
3811
|
+
async function annotateDebug(ctx, files) {
|
|
3812
|
+
const text = files.filter((f) => !f.binary && f.hunks.length > 0);
|
|
3813
|
+
const oldPathOf = (f) => f.oldPath ?? f.path;
|
|
3814
|
+
const none = () => /* @__PURE__ */ new Set();
|
|
3815
|
+
const [onNew, onOld] = await Promise.all([
|
|
3816
|
+
pathsWithMarkers(
|
|
3817
|
+
ctx,
|
|
3818
|
+
"new",
|
|
3819
|
+
text.filter((f) => f.status !== "deleted").map((f) => f.path)
|
|
3820
|
+
).catch(none),
|
|
3821
|
+
pathsWithMarkers(ctx, "old", text.filter((f) => f.status !== "added").map(oldPathOf)).catch(none)
|
|
3822
|
+
]);
|
|
3823
|
+
const read = async (p, side, marked) => {
|
|
3824
|
+
if (!marked.has(p)) return void 0;
|
|
3825
|
+
const content = await getFullFile(ctx, p, side).catch(() => null);
|
|
3826
|
+
return content === null ? void 0 : debugLineNumbers(content);
|
|
3827
|
+
};
|
|
3828
|
+
await mapLimit(
|
|
3829
|
+
text.filter((f) => onNew.has(f.path) || onOld.has(oldPathOf(f))),
|
|
3830
|
+
8,
|
|
3831
|
+
async (f) => {
|
|
3832
|
+
const [n, o] = await Promise.all([read(f.path, "new", onNew), read(oldPathOf(f), "old", onOld)]);
|
|
3833
|
+
markDebugLines(f, n, o);
|
|
3834
|
+
}
|
|
3835
|
+
);
|
|
3836
|
+
}
|
|
3749
3837
|
|
|
3750
3838
|
// packages/server/src/anchor.ts
|
|
3751
3839
|
function sideLines(diff, side) {
|
|
@@ -3895,9 +3983,9 @@ function cutHunk(hunk, picked, mode) {
|
|
|
3895
3983
|
});
|
|
3896
3984
|
return out;
|
|
3897
3985
|
}
|
|
3898
|
-
function pickedLines(diff, selection) {
|
|
3986
|
+
function pickedLines(diff, selection, skipDebug) {
|
|
3899
3987
|
const picked = /* @__PURE__ */ new Map();
|
|
3900
|
-
const changed = (h) => h.lines.map((l, i) => l.type === "context" ? -1 : i).filter((i) => i >= 0);
|
|
3988
|
+
const changed = (h) => h.lines.map((l, i) => l.type === "context" || skipDebug && l.debug ? -1 : i).filter((i) => i >= 0);
|
|
3901
3989
|
if (selection === void 0) {
|
|
3902
3990
|
for (const [i, h] of diff.hunks.entries()) picked.set(i, new Set(changed(h)));
|
|
3903
3991
|
return picked;
|
|
@@ -3921,10 +4009,11 @@ function pickedLines(diff, selection) {
|
|
|
3921
4009
|
}
|
|
3922
4010
|
return picked;
|
|
3923
4011
|
}
|
|
3924
|
-
function buildStagePatch(diff, mode, selection) {
|
|
4012
|
+
function buildStagePatch(diff, mode, selection, opts = {}) {
|
|
3925
4013
|
if (diff.binary) throw badRequest("binary files cannot be staged from here; use git add", "not_stageable");
|
|
3926
4014
|
const whole = selection === void 0;
|
|
3927
|
-
const
|
|
4015
|
+
const skipDebug = !!opts.skipDebug && mode === "stage";
|
|
4016
|
+
const picked = pickedLines(diff, selection, skipDebug);
|
|
3928
4017
|
let lines = 0;
|
|
3929
4018
|
for (const set of picked.values()) lines += set.size;
|
|
3930
4019
|
const changedTotal = diff.hunks.reduce((n, h) => n + h.lines.filter((l) => l.type !== "context").length, 0);
|
|
@@ -3957,7 +4046,11 @@ function buildStagePatch(diff, mode, selection) {
|
|
|
3957
4046
|
}
|
|
3958
4047
|
if (lines === 0) {
|
|
3959
4048
|
const headerOnly = whole && (modeChange || rename2 || (diff.status === "added" || diff.status === "deleted") && diff.hunks.length === 0);
|
|
3960
|
-
if (!headerOnly)
|
|
4049
|
+
if (!headerOnly) {
|
|
4050
|
+
if (skipDebug && [...pickedLines(diff, selection, false).values()].some((set) => set.size > 0))
|
|
4051
|
+
throw badRequest("every changed line picked is debug code; pick those lines one by one to stage them anyway", "debug_only");
|
|
4052
|
+
throw badRequest("no changed lines selected", "empty_selection");
|
|
4053
|
+
}
|
|
3961
4054
|
return { patch: header.join("\n") + "\n", lines: 0 };
|
|
3962
4055
|
}
|
|
3963
4056
|
let delta = 0;
|
|
@@ -4225,7 +4318,7 @@ function stateFilePath(repoRoot, baseDir = dataDir()) {
|
|
|
4225
4318
|
return path4.join(baseDir, repoHash(repoRoot), "state.json");
|
|
4226
4319
|
}
|
|
4227
4320
|
function defaultPrefs() {
|
|
4228
|
-
return { viewMode: "unified", nvimSocketByRoot: {}, autoRefresh: true, railOpen: false };
|
|
4321
|
+
return { viewMode: "unified", nvimSocketByRoot: {}, autoRefresh: true, railOpen: false, ignoreDebug: true };
|
|
4229
4322
|
}
|
|
4230
4323
|
function defaultState(repoRoot) {
|
|
4231
4324
|
return { schemaVersion: 1, repoRoot, targets: {}, issues: [], todos: [], prefs: defaultPrefs() };
|
|
@@ -4266,7 +4359,8 @@ function normalise(raw2, repoRoot) {
|
|
|
4266
4359
|
...r.prefs ?? {},
|
|
4267
4360
|
nvimSocketByRoot: r.prefs?.nvimSocketByRoot ?? {},
|
|
4268
4361
|
autoRefresh: typeof r.prefs?.autoRefresh === "boolean" ? r.prefs.autoRefresh : true,
|
|
4269
|
-
railOpen: typeof r.prefs?.railOpen === "boolean" ? r.prefs.railOpen : false
|
|
4362
|
+
railOpen: typeof r.prefs?.railOpen === "boolean" ? r.prefs.railOpen : false,
|
|
4363
|
+
ignoreDebug: typeof r.prefs?.ignoreDebug === "boolean" ? r.prefs.ignoreDebug : true
|
|
4270
4364
|
}
|
|
4271
4365
|
};
|
|
4272
4366
|
}
|
|
@@ -4863,6 +4957,7 @@ function createApp(opts) {
|
|
|
4863
4957
|
if (typeof body.lastTarget === "string") s.prefs.lastTarget = body.lastTarget;
|
|
4864
4958
|
if (typeof body.autoRefresh === "boolean") s.prefs.autoRefresh = body.autoRefresh;
|
|
4865
4959
|
if (typeof body.railOpen === "boolean") s.prefs.railOpen = body.railOpen;
|
|
4960
|
+
if (typeof body.ignoreDebug === "boolean") s.prefs.ignoreDebug = body.ignoreDebug;
|
|
4866
4961
|
if (body.nvimSocketByRoot && typeof body.nvimSocketByRoot === "object") {
|
|
4867
4962
|
s.prefs.nvimSocketByRoot = { ...s.prefs.nvimSocketByRoot, ...body.nvimSocketByRoot };
|
|
4868
4963
|
}
|
|
@@ -4874,6 +4969,7 @@ function createApp(opts) {
|
|
|
4874
4969
|
const key = c.req.param("key");
|
|
4875
4970
|
const ctx = await targetCtx(key);
|
|
4876
4971
|
const diffs = await listTargetDiffs(ctx);
|
|
4972
|
+
await annotateDebug(ctx, diffs);
|
|
4877
4973
|
listings.set(key, { at: Date.now(), files: diffs });
|
|
4878
4974
|
const notices = changedNotices.get(key) ?? /* @__PURE__ */ new Set();
|
|
4879
4975
|
changedNotices.set(key, notices);
|
|
@@ -4914,6 +5010,7 @@ function createApp(opts) {
|
|
|
4914
5010
|
const untracked = c.req.query("untracked") === "1" ? true : void 0;
|
|
4915
5011
|
const diff = await fileDiffWithHints(ctx, filePath, { oldPath, untracked });
|
|
4916
5012
|
if (!diff) throw notFound(`no diff for ${filePath} in ${key}`, "no_diff");
|
|
5013
|
+
await annotateDebug(ctx, [diff]);
|
|
4917
5014
|
return c.json(diff);
|
|
4918
5015
|
});
|
|
4919
5016
|
api.get("/targets/:key/file/full", async (c) => {
|
|
@@ -4950,11 +5047,13 @@ function createApp(opts) {
|
|
|
4950
5047
|
if (!body.path || typeof body.path !== "string") throw badRequest("missing path");
|
|
4951
5048
|
if (!body.contentHash || typeof body.contentHash !== "string") throw badRequest("contentHash is required");
|
|
4952
5049
|
if (body.hunks !== void 0 && !Array.isArray(body.hunks)) throw badRequest("hunks must be an array", "bad_selection");
|
|
5050
|
+
if (body.skipDebug !== void 0 && typeof body.skipDebug !== "boolean") throw badRequest("skipDebug must be a boolean", "bad_selection");
|
|
4953
5051
|
const diff = await fileDiffWithHints(ctx, body.path);
|
|
4954
5052
|
if (!diff) throw badRequest(`file ${body.path} is not part of ${key}`, "no_diff");
|
|
4955
5053
|
if (diff.contentHash !== body.contentHash)
|
|
4956
5054
|
throw new HttpError(409, "the diff changed since it was loaded; refresh and pick the lines again", "diff_changed");
|
|
4957
|
-
|
|
5055
|
+
if (body.skipDebug) await annotateDebug(ctx, [diff]);
|
|
5056
|
+
const { patch, lines } = buildStagePatch(diff, mode, body.hunks, { skipDebug: body.skipDebug });
|
|
4958
5057
|
await applyToIndex(ctx.cwd, patch, { reverse: mode === "unstage" });
|
|
4959
5058
|
void watchers.get(ctx.cwd)?.poll();
|
|
4960
5059
|
const res = { ok: true, lines };
|
|
@@ -5665,7 +5764,7 @@ async function startServer(opts) {
|
|
|
5665
5764
|
}
|
|
5666
5765
|
|
|
5667
5766
|
// bin/cli.ts
|
|
5668
|
-
var VERSION = true ? "0.
|
|
5767
|
+
var VERSION = true ? "0.14.0" : "dev";
|
|
5669
5768
|
function parseArgs(argv) {
|
|
5670
5769
|
const args = { repoPath: process.cwd(), open: true, updateCheck: true, help: false, version: false };
|
|
5671
5770
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2) format("woff2"),url(/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff) format("woff")}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:500;src:url(/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2) format("woff2"),url(/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff) format("woff")}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:600;src:url(/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2) format("woff2"),url(/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff) format("woff")}@font-face{font-family:IBM Plex Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2) format("woff2"),url(/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff) format("woff")}@font-face{font-family:IBM Plex Mono;font-style:normal;font-display:swap;font-weight:500;src:url(/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2) format("woff2"),url(/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff) format("woff")}:root{--ground: #eef0f3;--surface: #ffffff;--surface-2: #f5f6f8;--surface-3: #e4e7ec;--line: #d9dde3;--line-strong: #b7bdc7;--ink: #16181d;--ink-2: #454b58;--muted: #626977;--accent: #5b47d9;--accent-strong: #4a38bf;--accent-soft: #ebe8fb;--accent-line: #c4bbf2;--add-bg: #e3f5e7;--add-bg-strong: #bfe9c9;--add-fg: #1e7a3a;--del-bg: #fce8e8;--del-bg-strong: #f5c2c2;--del-fg: #c42b2b;--warn-bg: #f6e7b8;--warn-soft: #fdf8e6;--warn-fg: #8a6100;--warn-line: #d9b64a;--r-ctl: 3px;--r-card: 6px;--ctl-h: 26px;--t-meta: 11px;--t-ui: 13px;--t-lead: 15px;--t-figure: 26px;--lift: 0 4px 16px rgba(20, 24, 33, .13);--sans: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", sans-serif;--mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, "Noto Sans Mono CJK SC", monospace}*{box-sizing:border-box}html,body,#root{height:100%;margin:0}body{font:var(--t-ui) / 1.5 var(--sans);color:var(--ink);background:var(--ground);-webkit-font-smoothing:antialiased}button,input,select,textarea{font:inherit;color:inherit}button{height:var(--ctl-h);display:inline-flex;align-items:center;gap:6px;background:var(--surface);border:1px solid var(--line);border-radius:var(--r-ctl);padding:0 10px;cursor:pointer;white-space:nowrap}button:hover:not(:disabled){border-color:var(--line-strong);background:var(--surface-2)}button:disabled{opacity:.45;cursor:default}button.primary{background:var(--ink);border-color:var(--ink);color:#fff}button.primary:hover:not(:disabled){background:#2b2f38;border-color:#2b2f38}button.accent{background:var(--accent);border-color:var(--accent);color:#fff}button.accent:hover:not(:disabled){background:var(--accent-strong);border-color:var(--accent-strong)}button.link{height:auto;background:none;border:none;color:var(--ink-2);padding:0 4px}button.link:hover:not(:disabled){background:none;color:var(--ink);text-decoration:underline;text-underline-offset:2px}button.link.danger{color:var(--del-fg)}button.link.active{color:var(--accent);font-weight:500}button.icon{padding:0 7px}button.quiet{background:none;border-color:transparent;color:var(--ink-2)}button.quiet:hover:not(:disabled){background:var(--surface-3);border-color:transparent;color:var(--ink)}body.resizing{cursor:col-resize;-webkit-user-select:none;user-select:none}button.toggle:before{content:"";width:6px;height:6px;border-radius:50%;border:1.5px solid currentColor}button.toggle[aria-pressed=true]{border-color:var(--ink)}button.toggle[aria-pressed=true]:before{background:currentColor}input,select,textarea{height:var(--ctl-h);border:1px solid var(--line);border-radius:var(--r-ctl);padding:0 8px;background:var(--surface)}select{padding-right:4px}textarea{height:auto;width:100%;resize:vertical;font-family:var(--mono);font-size:12px;line-height:1.6;padding:6px 8px}input[type=checkbox]{height:auto;margin:0;accent-color:var(--ink)}input:focus,select:focus,textarea:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-soft)}button:focus-visible,[tabindex]:focus-visible{outline:2px solid var(--accent);outline-offset:1px}kbd{font-family:var(--mono);font-size:11px;background:var(--surface);border:1px solid var(--line);border-bottom-width:2px;border-radius:3px;padding:0 5px;color:var(--ink-2)}.mono{font-family:var(--mono);font-size:12px}.muted{color:var(--muted)}.small{font-size:11px}.spacer{flex:1}.add{color:var(--add-fg)}.del{color:var(--del-fg)}.check{display:inline-flex;align-items:center;gap:5px;white-space:nowrap;cursor:pointer}.badge{display:inline-flex;align-items:center;height:17px;padding:0 6px;border-radius:var(--r-ctl);font-family:var(--mono);font-size:11px;background:var(--surface-3);color:var(--ink-2);white-space:nowrap}.badge-active{background:var(--accent-soft);color:var(--accent)}.badge-exported{background:var(--surface-3);color:var(--muted)}.badge-orphaned{background:var(--warn-bg);color:var(--warn-fg)}.badge-open{background:var(--ink);color:#fff}.badge-closed{background:var(--surface-3);color:var(--muted)}.seg{display:inline-flex;gap:1px;padding:2px;background:var(--surface-3);border-radius:4px}.seg button{height:22px;padding:0 9px;background:transparent;border:1px solid transparent;border-radius:var(--r-ctl);color:var(--ink-2)}.seg button:hover:not(:disabled){background:transparent;border-color:transparent;color:var(--ink)}.seg button.active{background:var(--surface);border-color:var(--line);color:var(--ink);font-weight:500}.seg.small button{height:20px;padding:0 7px;font-size:12px}.seg button.active .tab-count{color:var(--ink)}.tabs{display:inline-flex;align-self:stretch;align-items:stretch;gap:2px}.tab{height:auto;background:none;border:none;border-radius:0;padding:0 6px;color:var(--ink-2);position:relative}.tab:hover:not(:disabled){background:none;color:var(--ink)}.tab.active{color:var(--ink);font-weight:500}.tab.active:after{content:"";position:absolute;left:6px;right:6px;bottom:-1px;height:2px;background:var(--ink)}.tab-count{font-family:var(--mono);font-size:11px;color:var(--muted)}.tab.active .tab-count{color:var(--ink)}.tabs.small .tab{padding:0 5px;font-size:12px}.error-box{color:var(--del-fg);background:var(--del-bg);padding:8px 12px;border-radius:var(--r-card);margin:8px}.empty{padding:20px 12px;text-align:center;color:var(--muted);font-size:12px;line-height:1.6}.app{display:flex;flex-direction:column;height:100%}.body{flex:1;display:flex;min-height:0}.main{flex:1;min-width:0;display:flex;flex-direction:column;background:var(--surface)}.loading-screen,.fatal{padding:48px;text-align:center;color:var(--muted)}.fatal h1{font-family:var(--mono);font-weight:500;font-size:20px;letter-spacing:-.02em;color:var(--ink)}.placeholder{padding:48px 24px;text-align:center;color:var(--muted)}.placeholder p{margin:6px 0}.topbar{display:flex;align-items:center;gap:14px;padding:0 10px 0 12px;min-height:40px;background:var(--ground);flex-wrap:wrap}.brand{display:flex;align-items:center;gap:7px}.mark{width:16px;height:16px;flex:none}.wordmark{font-family:var(--mono);font-weight:500;font-size:15px;letter-spacing:-.02em;color:var(--ink)}.scope{display:flex;align-items:center;gap:8px}.update-chip{height:auto;padding:1px 6px;border:none;font-family:var(--mono);font-size:var(--t-meta);color:var(--ink-2);background:var(--surface-3)}.update-chip:hover:not(:disabled){color:var(--ink);background:var(--line)}.repo-name{font-weight:500}.branch{font-family:var(--mono);font-size:11px;color:var(--ink-2);background:var(--surface-3);border-radius:var(--r-ctl);padding:1px 6px}.inline-form{display:inline-flex;align-items:center;gap:4px}.inline-form input{width:130px;font-family:var(--mono);font-size:12px}.topbar-right{margin-left:auto;display:flex;align-items:center;gap:6px}.nvim{display:inline-flex;align-items:center;gap:4px;max-width:300px}.nvim select{max-width:240px}.nvim-one,.nvim-idle{font-family:var(--mono);font-size:12px;color:var(--muted)}.rail-switch{margin-left:8px;background:none;border-color:transparent;color:var(--ink-2)}.rail-switch:hover:not(:disabled){background:var(--surface-3);border-color:transparent;color:var(--ink)}.rail-switch.active{background:var(--surface);border-color:var(--line);color:var(--ink)}.rail-switch-n{font-family:var(--mono);font-size:var(--t-meta);color:var(--muted)}.rail-switch.has-unexported .rail-switch-n{background:var(--accent-soft);color:var(--accent);border-radius:var(--r-ctl);padding:0 5px}.sidebar{flex:none;display:flex;flex-direction:column;background:var(--ground);overflow:hidden;position:relative}.side-nav{padding:8px 12px;flex-shrink:0}.side-view-select{width:100%;min-height:30px;font-weight:500}.side-slot,.side-form{flex:1;min-height:0;overflow:auto;display:flex;flex-direction:column;align-items:stretch;gap:10px;padding:12px 12px 16px}.side-form{overflow:visible;padding:0}.side-slot>.side-form{flex:none}.field{display:flex;flex-direction:column;gap:3px;font-size:var(--t-meta);color:var(--muted)}.field input{width:100%;font-size:var(--t-ui)}.field-row{display:flex;align-items:center;gap:6px}.field-row input{min-width:0;flex:1}.side-group{display:flex;flex-direction:column;gap:8px;padding-top:12px;border-top:1px solid var(--line)}.side-group-title{font-weight:600;color:var(--ink-2)}.side-form button{justify-content:center;background:var(--surface-2);border-color:var(--line-strong);font-weight:500}.side-form button:hover:not(:disabled){background:var(--surface-3);border-color:var(--line-strong)}.side-form button.toggle{justify-content:flex-start;font-weight:400}.side-form button.toggle[aria-pressed=true]{background:var(--ink);border-color:var(--ink);color:#fff}.side-form button.link{justify-content:flex-start;background:none;border:none;font-weight:400}.side-form-status{display:flex;align-items:center;gap:8px;font-variant-numeric:tabular-nums}.side-grip{position:absolute;top:0;right:0;bottom:0;width:5px;cursor:col-resize;background:var(--line);background-clip:content-box;border-left:4px solid transparent}.side-grip:hover{background:var(--line-strong)}.tree-blocks{flex:1;min-height:0;overflow:auto;display:flex;flex-direction:column}.tree-block{display:flex;flex-direction:column;flex:none;min-width:0}.tree-block+.tree-block{margin-top:6px;border-top:1px solid var(--line)}.tree-block.empty .block-head{padding:6px 12px}.progress{flex:none;padding:10px 12px 12px}.progress-figure{display:flex;align-items:baseline;gap:6px}.progress-done{font-family:var(--mono);font-size:var(--t-figure);font-weight:500;line-height:1.1;letter-spacing:-.03em;color:var(--ink);font-variant-numeric:tabular-nums}.progress-done.complete{color:var(--add-fg)}.progress-of{font-family:var(--mono);font-size:var(--t-lead);color:var(--muted);font-variant-numeric:tabular-nums}.progress-label{font-size:var(--t-meta);color:var(--muted)}.progress-track{margin-top:7px;height:3px;background:var(--surface-3);border-radius:2px;overflow:hidden}.progress-track>span{display:block;height:100%;background:var(--ink);transition:width .25s}.sidebar-target{flex:none;display:flex;align-items:center;gap:8px;padding:6px 10px;border-bottom:1px solid var(--line);white-space:nowrap}.sidebar-target-name{font-family:var(--mono);font-size:12px;color:var(--ink-2);min-width:0;overflow:hidden;text-overflow:ellipsis}.sidebar-target .link{font-size:12px}.block-head{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:8px;padding:8px 12px 4px;background:var(--ground);font-size:12px;white-space:nowrap}.block-title{font-weight:600}.block-n{font-family:var(--mono);font-size:var(--t-meta);color:var(--muted)}.block-head .counts{font-family:var(--mono);font-size:var(--t-meta);display:inline-flex;gap:4px}.tree-tools{display:inline-flex;gap:2px;opacity:0}.tree-block:hover .tree-tools,.tree-tools:focus-within{opacity:1}.tree-tools .link{font-size:var(--t-meta);color:var(--muted)}.tree{padding:0 0 4px}.tree-row{display:flex;align-items:center;gap:6px;padding:0 10px;height:24px;cursor:pointer;white-space:nowrap;-webkit-user-select:none;user-select:none}.tree-row:hover{background:var(--surface-3)}.tree-row.active{background:var(--surface);box-shadow:inset 3px 0 var(--ink);font-weight:500}.tree-row.active .name{color:var(--ink)}.tree-row.viewed .name{color:var(--muted)}.tree-row .name{overflow:hidden;text-overflow:ellipsis}.tree-row.dir .name{font-weight:500;color:var(--ink-2)}.chev{display:inline-block;width:12px;font-size:10px;color:var(--muted);transition:transform .1s}.chev.open{transform:rotate(90deg)}.tree-row .counts{margin-left:auto;font-family:var(--mono);font-size:11px;display:inline-flex;gap:4px}.tree-row .count{font-family:var(--mono);font-size:11px}.viewed-box{margin:0}.status{font-family:var(--mono);font-size:11px;font-weight:500;width:14px;text-align:center}.status-added{color:var(--add-fg)}.status-modified{color:var(--warn-fg)}.status-deleted{color:var(--del-fg)}.status-renamed{color:var(--ink-2)}.changed{font-size:11px;background:var(--warn-bg);color:var(--warn-fg);border-radius:var(--r-ctl);padding:0 5px}.cc{font-family:var(--mono);font-size:11px;background:var(--accent-soft);color:var(--accent);border-radius:var(--r-ctl);padding:0 5px}.diff-panel{flex:1;display:flex;flex-direction:column;min-height:0;position:relative}.file-head{display:flex;align-items:center;gap:10px;padding:0 14px;min-height:40px;border-bottom:1px solid var(--line);background:var(--surface);flex-wrap:wrap}.file-head .path{font-family:var(--mono);font-size:var(--t-lead);letter-spacing:-.01em}.file-head .path .dir{color:var(--muted)}.file-head .path .base{font-weight:500}.file-head .counts{font-family:var(--mono);font-size:11px;display:inline-flex;gap:4px}.file-head .check{font-size:12px}.diff-scroll{flex:1;overflow:auto;font-family:var(--mono);font-size:12px;line-height:20px;tab-size:4;background:var(--surface)}.diff-scroll.selecting,.diff-scroll.picking{-webkit-user-select:none;user-select:none;cursor:row-resize}.diff-scroll.reattaching .add-btn{background:var(--warn-fg)}.vrow{will-change:transform}.row{display:flex;min-height:20px}.row.line,.cell{position:relative}.pin{position:absolute;right:0;top:0;bottom:0;width:3px;background:var(--accent)}.pin.muted{background:var(--line-strong)}.pill{position:absolute;right:10px;top:2px;height:16px;min-width:16px;padding:0 5px;justify-content:center;border:none;border-radius:8px;background:var(--accent);color:#fff;font-family:var(--sans);font-size:11px;line-height:16px}.pill:hover:not(:disabled){background:var(--accent-strong);border-color:transparent}.pill.muted{background:var(--line-strong)}.row.hunk-head{align-items:center;background:var(--surface-2);color:var(--muted);font-size:11.5px;padding:3px 12px 3px 128px;border-top:1px solid var(--line);border-bottom:1px solid var(--line);white-space:pre-wrap}.hunk-text{flex:1;min-width:0}.hunk-stage{font-family:var(--sans);font-size:11px;color:var(--muted);white-space:nowrap}.stagec{flex:0 0 18px;position:relative;-webkit-user-select:none;user-select:none}.stage-box{position:absolute;left:4px;top:4px;width:12px;height:12px;padding:0;border:1.5px solid var(--ink-2);border-radius:2px;background:var(--surface);opacity:0;cursor:pointer;z-index:1}.stage-box:hover:not(:disabled){background:var(--surface-3);border-color:var(--ink)}.row:hover .stage-box,.cell:hover .stage-box,.stage-box.on{opacity:1}.stage-box.on,.stage-box.on:hover:not(:disabled){background:var(--ink);border-color:var(--ink)}.stage-box.on:after{content:"";position:absolute;left:3px;top:0;width:3px;height:6px;border:solid #fff;border-width:0 1.5px 1.5px 0;transform:rotate(45deg)}.row.line.picked,.cell.picked{box-shadow:inset 3px 0 var(--ink)}.row.line.add.picked,.cell.add.picked{background:var(--add-bg-strong)}.row.line.del.picked,.cell.del.picked{background:var(--del-bg-strong)}.stage-bar{position:absolute;left:50%;bottom:16px;transform:translate(-50%);display:flex;align-items:center;gap:8px;padding:6px 8px 6px 12px;background:var(--surface);border:1px solid var(--line-strong);border-radius:var(--r-card);box-shadow:0 6px 20px #16181d24;font-size:12px;white-space:nowrap;z-index:3}.stage-bar-count{margin-right:4px}.stage-bar kbd{margin-left:2px}.stage-bar .primary kbd{background:#ffffff29;border-color:#ffffff4d;color:#fff}.tree-row .stage-file{display:none;font-size:11px;padding:0 2px}.tree-row:hover .stage-file{display:inline-flex}.row.gap-row{background:var(--surface-2);justify-content:center;gap:16px;padding:2px 0;border-top:1px solid var(--line);border-bottom:1px solid var(--line);font-family:var(--sans);font-size:12px}.gap-row .link{color:var(--muted)}.row.debug-row{background:repeating-linear-gradient(-45deg,var(--surface-2) 0 6px,var(--surface) 6px 12px);border-top:1px dashed var(--line-strong);border-bottom:1px dashed var(--line-strong);padding:2px 0 2px 60px;font-family:var(--sans);font-size:12px}.row.debug-row.open{border-bottom-style:none}.debug-row .link{color:var(--muted);text-decoration:none}.debug-row .link:hover{color:var(--ink)}.row.attach{background:var(--surface-2);border-top:1px solid var(--line);border-bottom:1px solid var(--line);padding:6px 12px 6px 60px;font-family:var(--sans);font-size:13px;display:block}.gut{flex:0 0 48px;text-align:right;padding-right:6px;font-size:11px;color:var(--muted);cursor:pointer;-webkit-user-select:none;user-select:none}.gut:hover{color:var(--ink)}.addc{flex:0 0 18px;position:relative}.add-btn{position:absolute;left:0;top:2px;width:16px;height:16px;padding:0;justify-content:center;line-height:16px;border-radius:var(--r-ctl);background:var(--accent);color:#fff;border:none;font-weight:600;font-size:13px;opacity:0;cursor:pointer;z-index:1}.add-btn:hover:not(:disabled){background:var(--accent-strong);border-color:transparent}.row:hover .add-btn,.cell:hover .add-btn{opacity:1}.add-btn.static{position:static;display:inline-flex;opacity:1;width:16px;height:16px;font-size:11px;vertical-align:-3px}.foldc{flex:0 0 14px;position:relative;-webkit-user-select:none;user-select:none}.fold-btn{position:absolute;inset:0;height:auto;display:flex;align-items:center;justify-content:center;padding:0;border:0;background:none;color:var(--muted);cursor:pointer;opacity:0}.fold-btn svg{transform:rotate(90deg);transition:transform .1s}.fold-btn.shut svg{transform:none}.row.line:hover .fold-btn,.cell:hover .fold-btn,.fold-btn.shut,.fold-btn:focus-visible{opacity:1}.fold-btn:hover:not(:disabled){background:none;color:var(--ink)}.fold-chip{margin-left:8px;padding:0 6px;border:1px solid var(--line);border-radius:var(--r-ctl);background:var(--surface-2);color:var(--muted);font-family:var(--sans);font-size:var(--t-meta);line-height:14px;cursor:pointer;display:inline-block;height:16px;vertical-align:top;margin-top:2px}.fold-chip:hover{border-color:var(--line-strong);color:var(--ink)}.fold-chip .add{color:var(--add-fg)}.fold-chip .del{color:var(--del-fg)}.marker{flex:0 0 14px;text-align:center;-webkit-user-select:none;user-select:none;color:var(--muted)}.code{flex:1;min-width:0;white-space:pre-wrap;word-break:break-all;padding-right:40px}.nonl{color:var(--del-fg);margin-left:6px}.row.line.add,.cell.add{background:var(--add-bg)}.row.line.del,.cell.del{background:var(--del-bg)}.row.line.add .marker{color:var(--add-fg)}.row.line.del .marker{color:var(--del-fg)}.row.expanded,.row.pair.expanded .cell{background:var(--surface);color:var(--muted)}.row.line.selected,.cell.selected{background:var(--accent-soft)!important}.row.line.focused,.cell.focused{background:var(--accent-soft)!important;box-shadow:inset 3px 0 var(--accent)}.row.line.flash,.cell.flash{animation:flash 1.6s ease-out}@keyframes flash{0%{background:var(--accent-line)}to{background:transparent}}.row.pair{display:grid;grid-template-columns:1fr 1fr}.cell{display:flex;min-width:0;border-left:1px solid var(--line)}.cell:first-child{border-left:none}.cell.blank{background:var(--surface-2)}.diff-list.split .row.hunk-head{padding-left:78px}.comment-card{position:relative;background:var(--surface);border:1px solid var(--line);border-left:3px solid var(--accent);border-radius:var(--r-card);padding:7px 10px 8px}.comment-card.status-exported{border-left-color:var(--line-strong)}.comment-card.status-orphaned{border-color:var(--warn-line);border-left-style:dashed;background:var(--warn-soft)}.comment-card.selected{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-soft)}.comment-head{display:flex;align-items:center;gap:8px;min-height:20px;font-size:12px;color:var(--ink-2);flex-wrap:wrap}.comment-head .mono{font-size:11px}.comment-head .time{font-size:11px;color:var(--muted)}.card-actions{position:absolute;right:6px;top:5px;display:inline-flex;align-items:center;gap:2px;padding-left:8px;border-radius:var(--r-ctl);background:inherit;opacity:0;transition:opacity .1s}.card-actions .link{font-size:12px}.comment-card:hover .card-actions,.comment-card:focus-within .card-actions,.comment-card.reattaching .card-actions{opacity:1}.comment-editor{background:var(--surface);border:1px solid var(--accent);border-left-width:3px;border-radius:var(--r-card);padding:8px 10px;max-width:900px}.comment-editor-head{font-family:var(--mono);font-size:11px;color:var(--muted);margin-bottom:6px}.comment-editor textarea{border-color:var(--line)}.comment-editor-actions{display:flex;gap:6px;margin-top:8px}.comment-editor .primary{background:var(--accent);border-color:var(--accent)}.comment-editor .primary:hover:not(:disabled){background:var(--accent-strong);border-color:var(--accent-strong)}.snippet{font-family:var(--mono);font-size:11.5px;line-height:18px;background:var(--surface-2);border-radius:var(--r-ctl);padding:4px 8px;margin:6px 0;overflow-x:auto;white-space:pre}.snippet .ln{display:inline-block;width:40px;color:var(--muted);text-align:right;margin-right:10px;-webkit-user-select:none;user-select:none}.md{font-size:13px}.md p{margin:4px 0}.md pre{background:var(--surface-2);padding:6px 8px;border-radius:var(--r-ctl);overflow-x:auto}.md code{font-family:var(--mono);font-size:12px;background:var(--surface-2);padding:0 3px;border-radius:3px}.md pre code{background:none;padding:0}.md ul,.md ol{padding-left:20px;margin:4px 0}.md blockquote{margin:4px 0;padding-left:10px;border-left:3px solid var(--line);color:var(--muted)}.commits-panel{flex:1;display:flex;flex-direction:column;min-height:0}.commits-panel[hidden]{display:none}.commits-list{flex:1;overflow:auto;background:var(--surface)}.commit-day{position:sticky;top:0;z-index:1;display:flex;align-items:center;height:24px;padding:0 12px;background:var(--ground);border-bottom:1px solid var(--line);color:var(--ink-2);font-size:12px;font-weight:500}.commit-row{display:flex;align-items:center;gap:10px;height:30px;padding:0 12px;border-bottom:1px solid var(--surface-3);cursor:pointer;white-space:nowrap}.commit-row:hover{background:var(--surface-2)}.commit-row.active{background:var(--surface-2);box-shadow:inset 2px 0 var(--ink)}.commit-row .sha{flex:0 0 auto;min-width:7ch;color:var(--ink-2)}.commit-row .subject{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis}.commit-row .subject .pr{margin-left:6px;color:var(--muted);font-family:var(--mono);font-size:12px}.commit-row .refs{display:inline-flex;gap:4px;flex:0 1 auto;min-width:0;overflow:hidden}.ref{display:inline-block;max-width:180px;height:17px;padding:0 7px;border:1px solid var(--line-strong);border-radius:9px;color:var(--ink-2);font-family:var(--mono);font-size:11px;line-height:15px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ref-tag{background:var(--surface-3);border-color:var(--surface-3)}.ref-head{background:var(--ink);border-color:var(--ink);color:#fff}.ref-more{border-style:dashed;color:var(--muted)}.ref-fork{border-style:dashed;border-color:var(--ink-2);color:var(--ink)}.commit-row.fork{border-top:1px dashed var(--line-strong)}.fork-note{font-size:var(--t-meta);line-height:1.6}.fork-note .link{padding:0;font-size:var(--t-meta)}.commit-row .author{flex:0 0 110px;overflow:hidden;text-overflow:ellipsis;color:var(--ink-2);font-size:12px}.commit-row .time{flex:0 0 40px;text-align:right;color:var(--muted);font-family:var(--mono);font-size:11px}.commits-foot{display:flex;justify-content:center;align-items:center;min-height:44px;font-size:12px}.worktrees-panel{flex:1;display:flex;flex-direction:column;min-height:0;background:var(--surface)}.wt-list{flex:1;overflow:auto;max-width:740px;padding:16px 20px 24px;display:flex;flex-direction:column;gap:8px}.wt-row{display:flex;align-items:flex-start;gap:8px;padding:8px 10px;border:1px solid var(--line);border-radius:var(--r-card);background:var(--surface)}.wt-row.active{border-color:var(--line-strong);box-shadow:inset 3px 0 var(--ink)}.wt-row.gone .wt-name{color:var(--muted);text-decoration:line-through}.wt-row.free{border-style:dashed;background:var(--surface-2)}.wt-row.free .wt-name{color:var(--ink-2)}.wt-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.wt-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.wt-name{font-weight:500;overflow-wrap:anywhere}.wt-path{font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wt-actions{display:flex;align-items:center;gap:2px;flex:0 0 auto}.wt-comparison{display:flex;align-items:center;gap:10px;flex-wrap:wrap;font-size:12px}.wt-commit-count{display:inline-flex;align-items:center;gap:3px;font-variant-numeric:tabular-nums}.wt-extras{display:flex;align-items:center;flex-wrap:wrap;gap:4px 10px}.wt-extras>.row-actions{margin:0;gap:2px}.wt-expanded{flex-basis:100%;min-width:0;margin-top:4px;padding:12px;background:var(--surface-2);border-radius:var(--r-card);overflow-wrap:anywhere}.wt-commit{display:flex;align-items:baseline;gap:10px;width:100%;padding:8px 0;border:0;border-bottom:1px solid var(--line);background:transparent;text-align:left;white-space:normal}.wt-commit>span{flex:1;min-width:0}.wt-commit small{color:var(--muted)}.wt-todo{padding:8px 0;border-bottom:1px solid var(--line)}.wt-todo summary{display:flex;align-items:center;gap:8px;cursor:pointer}.wt-todo.done summary>span:last-child{text-decoration:line-through;color:var(--muted)}.wt-tmux>*+*{margin-top:8px}@media(max-width:650px){.wt-commit{flex-wrap:wrap}}.wt-confirm{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:6px;padding:7px 9px;border-radius:var(--r-ctl);background:var(--warn-soft);border:1px solid var(--warn-line);color:var(--warn-fg);font-size:12px}.wt-confirm .link{color:var(--warn-fg)}.wt-confirm .link.danger{color:var(--del-fg)}.wt-empty{padding:20px 2px}.rail-list.tasks{padding:4px 0 12px;gap:0}.task-add{height:34px;margin:0 4px 2px;padding:0 10px 0 24px;justify-content:flex-start;gap:10px;border:none;border-radius:var(--r-card);background:none;color:var(--ink-2);font-weight:500}.task-add:hover:not(:disabled){background:var(--surface-3);border-color:transparent;color:var(--ink)}.task-add-plus{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;background:var(--ink);color:#fff;font-size:15px;line-height:1}.task-list{display:flex;flex-direction:column}.task-row{position:relative;display:flex;align-items:flex-start;gap:6px;margin:0 4px;padding:5px 10px 5px 4px;border-radius:var(--r-card)}.task-row:hover,.task-row.open{background:var(--surface)}.task-row.open{box-shadow:0 0 0 1px var(--line)}.task-grip{flex:0 0 14px;height:20px;display:inline-flex;align-items:center;justify-content:center;color:var(--muted);cursor:grab;visibility:hidden}.task-row:hover .task-grip{visibility:visible}.task-list.dragging .task-grip{visibility:hidden}.task-check{position:relative;flex:0 0 18px;width:18px;height:18px;margin-top:1px;padding:0;border:1.5px solid var(--ink-2);border-radius:50%;background:var(--surface)}.task-check:after{content:"";position:absolute;left:5px;top:1.5px;width:4px;height:8px;border:solid var(--ink);border-width:0 1.5px 1.5px 0;transform:rotate(45deg);opacity:0}.task-check:hover:not(:disabled){background:var(--surface);border-color:var(--ink)}.task-check:hover:not(:disabled):after{opacity:.45}.task-row.done .task-check,.task-row.completing .task-check{background:var(--ink);border-color:var(--ink)}.task-row.done .task-check:after,.task-row.completing .task-check:after{border-color:#fff;opacity:1}.task-row.draft .task-check{border-style:dashed;border-color:var(--muted)}.task-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.task-line{display:flex;align-items:flex-start;gap:6px}.task-ta{width:100%;min-width:0;height:auto;padding:0;border:none;border-radius:0;background:transparent;resize:none;overflow:hidden;font-family:var(--sans);line-height:20px}.task-ta:focus{box-shadow:none}.task-title{flex:1;font-size:13px}.task-body{font-size:12px;line-height:18px;color:var(--ink-2)}.task-details{font-size:12px;line-height:18px;color:var(--muted);cursor:text}.task-details .md{font-size:12px}.task-details .md p{margin:0 0 2px}.task-row.done .task-title,.task-row.completing .task-title{text-decoration:line-through;color:var(--muted)}.task-row.completing{opacity:.55;transition:opacity .3s}.task-row .badge{margin-top:2px}.task-count{font-family:var(--mono);font-size:11px;line-height:20px;color:var(--muted);white-space:nowrap}.task-actions{display:inline-flex;align-items:center;gap:2px;margin-left:auto;line-height:20px;white-space:nowrap;visibility:hidden}.task-actions .link{font-size:12px}.task-row:hover .task-actions,.task-row:focus-within .task-actions,.task-sub:hover .task-actions{visibility:visible}.task-row.dragging{opacity:.4}.task-row.drop-before{box-shadow:0 -2px 0 0 var(--ink)}.task-row.drop-after{box-shadow:0 2px 0 0 var(--ink)}.task-empty{padding:14px 20px;color:var(--muted);font-size:12px}.task-add-hint{padding:0 10px 6px 46px;font-size:12px;color:var(--muted)}.task-done{margin-top:8px;padding-top:4px;border-top:1px solid var(--line)}.task-done-head{display:flex;align-items:center;min-height:28px;padding:0 14px 0 10px}.task-done-toggle{height:auto;padding:0 4px;gap:6px;border:none;background:none;color:var(--ink-2);font-weight:500}.task-done-toggle:hover:not(:disabled){background:none;border-color:transparent;color:var(--ink)}.task-list-pick{max-width:220px}.task-subs{display:flex;flex-direction:column;gap:4px;margin-top:6px}.task-sub{display:flex;align-items:flex-start;gap:8px;padding:4px 6px;border-radius:var(--r-ctl);background:var(--surface-2);font-size:12px}.task-sub-dot{flex:0 0 8px;width:8px;height:8px;margin-top:6px;border-radius:50%;background:var(--accent)}.task-sub.status-exported .task-sub-dot{background:var(--line-strong)}.task-sub.status-orphaned .task-sub-dot{background:var(--warn-line)}.task-sub-main{flex:1;min-width:0}.task-sub-head{display:flex;align-items:center;gap:6px;flex-wrap:wrap}.task-sub-head .mono{font-size:11px}.task-sub-body{color:var(--muted);white-space:pre-wrap;max-height:54px;overflow:hidden}.task-sub-add{align-self:flex-start;font-size:12px}.task-sub-none{font-size:12px;color:var(--muted)}.toast{position:fixed;bottom:20px;left:50%;transform:translate(-50%);background:var(--ink);color:#fff;padding:8px 16px;border-radius:var(--r-card);box-shadow:0 6px 20px #16181d40;z-index:100;max-width:80vw}.toast-error{background:var(--del-fg)}.toast-action{height:22px;margin-left:14px;padding:0 8px;background:#ffffff24;border-color:#ffffff59;color:#fff;font-weight:500}.toast-action:hover:not(:disabled){background:#ffffff42;border-color:#ffffff80}.rail{width:360px;flex:none;border-left:1px solid var(--line);display:flex;flex-direction:column;background:var(--ground);min-height:0}.rail.rail-wide{width:440px}.rail-head{display:flex;align-items:center;gap:10px;padding:0 10px;min-height:36px;border-bottom:1px solid var(--line)}.rail-tools{display:flex;align-items:center;gap:8px;padding:0 10px;min-height:34px;border-bottom:1px solid var(--line);font-size:12px}.rail-notice{padding:6px 10px;background:var(--warn-bg);color:var(--warn-fg);border-bottom:1px solid var(--warn-line);display:flex;gap:8px;align-items:center;font-size:12px}.rail-notice .link{color:var(--warn-fg)}.rail-list{flex:1;overflow:auto;padding:10px;display:flex;flex-direction:column;gap:8px}.rail-slot{display:flex;flex-direction:column;gap:8px}.rail-foot{display:flex;flex-direction:column;gap:8px;padding:10px;border-top:1px solid var(--line);background:var(--ground)}.rail-foot-row{display:flex;align-items:center;gap:10px}.send{height:30px;padding:0 14px;background:var(--accent);border-color:var(--accent);color:#fff;font-weight:500}.send:hover:not(:disabled){background:var(--accent-strong);border-color:var(--accent-strong)}.send-n{font-family:var(--mono);font-size:11px;font-weight:400;line-height:16px;padding:0 5px;border-radius:var(--r-ctl);background:#ffffff38}.rail .comment-card{cursor:pointer}.rail .comment-card.focused{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-soft)}.comment-head .file{color:var(--muted);max-width:120px;overflow:hidden;text-overflow:ellipsis}.rail .comment-editor{max-width:none}@media(prefers-reduced-motion:reduce){.chev,.card-actions,.task-row.completing,.progress-track>span{transition:none}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}.action-icon{display:inline-flex;align-items:center;justify-content:center;gap:5px;vertical-align:middle;line-height:1}.action-icon>svg{flex-shrink:0}button:has(>.action-icon){display:inline-flex;align-items:center;justify-content:center;min-width:30px;min-height:30px;padding:6px}button.link:has(>.action-icon){color:var(--muted);border-radius:5px}button.link:has(>.action-icon):hover:not(:disabled){color:var(--accent);background:var(--accent-soft);text-decoration:none}button.link.danger:has(>.action-icon){color:var(--del-fg)}button.link.danger:has(>.action-icon):hover:not(:disabled){background:var(--del-bg)}.nvim>span{display:inline-flex;align-items:center;padding:4px}.nvim{position:relative}.nvim summary{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border:1px solid var(--line);border-radius:6px;cursor:pointer;list-style:none}.nvim summary::-webkit-details-marker{display:none}.nvim summary:hover{background:var(--surface-3)}.nvim summary:focus-visible{outline:1px solid var(--accent)}.nvim[open] summary{background:var(--accent-soft);color:var(--accent)}.nvim-popover{position:absolute;z-index:30;left:0;top:calc(100% + 6px);display:flex;align-items:center;gap:8px;width:320px;max-width:calc(100vw - 32px);padding:12px;background:var(--surface);border:1px solid var(--line);border-radius:8px;box-shadow:0 4px 16px #1f23281a}.nvim-popover>:first-child{flex:1;min-width:0;overflow-wrap:anywhere}.tree-icon{flex:0 0 16px;color:var(--muted)}.tree-icon-folder{color:#8c9aab;fill:#8c9aab12}.tree-row{display:flex;align-items:center;gap:6px;padding:0 8px;min-height:32px;border-radius:5px;cursor:pointer;white-space:nowrap;-webkit-user-select:none;user-select:none;line-height:20px}.tree-row.active{background:#eaf3ff;box-shadow:inset 1px 0 var(--accent)}.tree-row.active .tree-file-open{color:var(--accent)}.tree-row:focus-within{outline:1px solid var(--accent);outline-offset:-1px}.tree-row.dir .name{font-weight:500}.tree-row.dir{width:100%;border:0;background:transparent;text-align:left;color:var(--muted)}.tree-row.dir:hover{background:var(--surface-2)}.tree-row.dir.viewed .name{font-weight:400;color:var(--muted)}.tree-row.dir.viewed .tree-icon-folder{opacity:.55}.tree-row.dir.viewed .count{display:inline-flex;align-items:center;gap:3px}.tree-row.dir.viewed .count .tree-icon{color:var(--add-fg);width:12px;height:12px;flex-basis:12px}.tree-row .status{display:inline-flex;align-items:center;justify-content:center;flex:0 0 14px;height:18px;font-family:var(--sans);font-size:11px;font-weight:500;line-height:1}.comment-editor-head{font-family:var(--mono);font-size:12px;color:var(--muted);margin:-8px -10px 8px;padding:8px 19px;background:var(--surface-2);border-bottom:1px solid var(--line);border-radius:5px 5px 0 0}.block-heading{display:flex;align-items:center;gap:7px;min-height:26px}.block-summary{display:flex;align-items:center;gap:10px;margin-top:5px;font-size:10px;font-variant-numeric:tabular-nums}.tree-tools{display:inline-flex;gap:2px}.tree-tools button{display:grid;place-items:center;width:26px;height:26px;padding:0;border:none;background:transparent;color:var(--muted)}.tree-empty{display:flex;align-items:center;gap:8px;padding:16px 10px;color:var(--muted);font-size:12px}.block-head{flex-shrink:0;padding:9px 12px 10px;border-bottom:1px solid var(--line);background:#f8fafc;font-size:12px;white-space:nowrap}.block-indicator{width:6px;height:6px;border-radius:50%;background:#bf8700;flex-shrink:0}.block-indicator.is-staged{background:var(--add-fg)}.block-warn{padding:0 6px;border:1px solid var(--warn-line);border-radius:5px;background:var(--warn-soft);color:var(--warn-fg);font-size:10px;font-weight:600;line-height:17px;white-space:nowrap}.side-toggle{flex:none;display:flex;align-items:center;gap:6px;padding:0 12px 8px;color:var(--ink-2);font-size:var(--t-meta);cursor:pointer}.block-file-count{padding:0 6px;border:1px solid var(--line);border-radius:5px;color:var(--muted);font-size:10px;line-height:17px;font-variant-numeric:tabular-nums}.block-diff-counts{display:flex;gap:7px;font-family:var(--mono)}.action-count{font-size:10px;font-weight:500;font-variant-numeric:tabular-nums;line-height:1}.icon-spinning{animation:icon-spin 1s linear infinite}.tree-file-icon{display:inline-flex;width:16px;height:16px;flex:0 0 16px}.tree-file-icon svg{width:100%;height:100%}.tree-file-open{display:flex;align-items:center;gap:7px;flex:1;min-width:0;align-self:stretch;padding:0;border:0;border-radius:0;background:transparent;text-align:left}.tree-file-open:hover:not(:disabled){background:transparent}.tree-file-open:focus-visible{outline:none}.tree-row .name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis}.tree-row .counts{margin-left:auto;font-family:var(--mono);font-size:11px;display:inline-flex;gap:6px;flex-shrink:0;font-variant-numeric:tabular-nums;min-width:48px;justify-content:flex-end}.tree-row .count{font-size:11px;margin-left:auto;padding:0 4px}.tree-changed{width:6px;height:6px;flex:0 0 6px;border-radius:50%;background:#bf8700}.tree-binary{color:var(--muted);font-size:9px;letter-spacing:.04em}.tree-block .block-head{display:block}.tree-tools{opacity:1;margin-left:auto}
|