@xbghc/warden 0.3.0 → 0.5.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 +26 -6
- package/dist/cli.js +98 -20
- package/dist/web/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/dist/web/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/dist/web/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/dist/web/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/dist/web/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
- package/dist/web/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
- package/dist/web/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
- package/dist/web/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
- package/dist/web/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
- package/dist/web/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
- package/dist/web/assets/index-BRm8bZtC.css +1 -0
- package/dist/web/assets/index-CZMo0jXP.js +194 -0
- package/dist/web/index.html +3 -3
- package/package.json +1 -1
- package/dist/web/assets/index-CUBjyx71.js +0 -192
- package/dist/web/assets/index-D8iWq0bG.css +0 -1
package/README.md
CHANGED
|
@@ -4,7 +4,8 @@ Local web UI for reviewing git diffs — built for reviewing changes that a codi
|
|
|
4
4
|
with line comments you can copy back to the agent as a prompt.
|
|
5
5
|
|
|
6
6
|
- Runs as a single local process per repository (`127.0.0.1` only, no auth, no database).
|
|
7
|
-
- Diff sources: working tree, staged, working tree vs HEAD,
|
|
7
|
+
- Diff sources: working tree, staged, working tree vs HEAD, a branch since it forked off its base (commits and
|
|
8
|
+
uncommitted work together), any commit, any two refs, and git worktrees.
|
|
8
9
|
- Side-by-side **Unstaged** and **Staged** file lists, so staging a hunk in your editor is what marks it reviewed.
|
|
9
10
|
- GitHub-style unified / side-by-side diff with syntax highlighting, collapsed file tree, lazy per-file loading, context expansion, virtual scrolling.
|
|
10
11
|
- Line comments (single line or a dragged range), Markdown, edit / delete.
|
|
@@ -14,7 +15,9 @@ with line comments you can copy back to the agent as a prompt.
|
|
|
14
15
|
- Auto-refresh — warden watches the repository and reloads itself when you edit, stage, commit or switch branches.
|
|
15
16
|
- Local issues (title, Markdown body, open/closed) that link comments.
|
|
16
17
|
- Branch-scoped todos, exportable as a numbered checklist.
|
|
17
|
-
- Commit history browser
|
|
18
|
+
- Commit history browser: grouped by day, branch / tag labels, search by message, sha, author or
|
|
19
|
+
path (each a `git log` on the server, not a filter over the rows already loaded), and a
|
|
20
|
+
first-parent view that folds merged branches into their merge commits.
|
|
18
21
|
- Click a line number to jump to that line in a running nvim instance (WSL2 friendly).
|
|
19
22
|
- Read-only with respect to git: only whitelisted read sub-commands are ever executed.
|
|
20
23
|
|
|
@@ -45,10 +48,12 @@ Several instances on the same repository can run at the same time.
|
|
|
45
48
|
| Working tree, uncommitted (incl. untracked) | `working` | `git diff` + `git diff --no-index /dev/null <file>` |
|
|
46
49
|
| Staged | `staged` | `git diff --cached` |
|
|
47
50
|
| Working tree, everything vs HEAD | `all` | `git diff HEAD` (+ untracked) |
|
|
51
|
+
| Branch, everything since it forked off a base (committed or not) | `base:<ref>` | `git diff $(git merge-base <ref> HEAD)` (+ untracked) |
|
|
48
52
|
| One commit | `commit:<sha>` | `git diff <sha>^ <sha>` |
|
|
49
53
|
| Two refs | `range:<base>..<head>` | `git diff <base>...<head>` |
|
|
50
54
|
| Worktree, working tree | `worktree:<path>:working` | same, run inside the worktree |
|
|
51
55
|
| Worktree, branch vs base | `worktree:<path>:range:<base>..<head>` | same, run inside the worktree |
|
|
56
|
+
| Worktree, everything since base | `worktree:<path>:base:<ref>` | same, run inside the worktree |
|
|
52
57
|
|
|
53
58
|
Refs accept anything git can resolve (`main`, `v1.2`, `HEAD~3`, a sha). `@` is `HEAD`.
|
|
54
59
|
Worktrees are discovered with `git worktree list` and share the review state of the main repository.
|
|
@@ -57,7 +62,17 @@ Worktrees are discovered with `git worktree list` and share the review state of
|
|
|
57
62
|
selected the sidebar shows two blocks — Unstaged (`working`) and Staged (`staged`) — instead of a
|
|
58
63
|
single tree, and clicking a file switches to the view it belongs to. A file that is only partly
|
|
59
64
|
staged appears in both. Switching between the three keeps your comments, the draft you are typing
|
|
60
|
-
and the current selection; only the diff is reloaded. Commit and
|
|
65
|
+
and the current selection; only the diff is reloaded. Commit, range and `base` targets keep the single tree.
|
|
66
|
+
|
|
67
|
+
`base:<ref>` is for a branch a coding agent has been working on, committing as it goes: one tree with
|
|
68
|
+
everything since the branch forked off `<ref>` — the commits plus whatever is still uncommitted or
|
|
69
|
+
untracked. The diff runs against the merge base, so commits that landed on `<ref>` after the fork are
|
|
70
|
+
not listed as reverted (which is what a plain `git diff <ref>` would do). The *Branch* button picks the
|
|
71
|
+
base for you — the main worktree's branch when a sibling worktree is under review, otherwise `main` or
|
|
72
|
+
`master` — and the field next to it takes any ref. Unlike the three local views, `base` keeps its own
|
|
73
|
+
pool of comments and a commit never deletes them: the round under review is not over when the agent
|
|
74
|
+
commits, so a comment whose lines changed stays *orphaned*, snippet and all, until you have checked
|
|
75
|
+
the fix and delete or re-attach it.
|
|
61
76
|
|
|
62
77
|
## Keyboard
|
|
63
78
|
|
|
@@ -120,6 +135,9 @@ them — the comment's current view first, then `working`, `staged`, `all`. So:
|
|
|
120
135
|
**deleted** (and unlinked from any issue), because the code they were about is now history.
|
|
121
136
|
Anything still visible in Unstaged or Staged survives, including as a context line.
|
|
122
137
|
|
|
138
|
+
Commit, range and `base` targets each keep their own pool and only ever search themselves, and none of
|
|
139
|
+
them deletes on a moved HEAD.
|
|
140
|
+
|
|
123
141
|
Comment markers in the diff belong to one view; the rail's *全部* tab lists the whole pool and
|
|
124
142
|
*此文件* lists every comment on the open file regardless of which view it currently sits in.
|
|
125
143
|
|
|
@@ -140,8 +158,10 @@ refreshes by hand.
|
|
|
140
158
|
## Todos
|
|
141
159
|
|
|
142
160
|
Notes attached to a branch rather than to a line of code, for the things you notice while reviewing
|
|
143
|
-
that do not belong in a comment. They are not deleted when you commit.
|
|
144
|
-
|
|
161
|
+
that do not belong in a comment. They are not deleted when you commit. They sit in the right-hand
|
|
162
|
+
rail next to the comments, under the *Todo* tab: type a title and press Enter to add one, expand a
|
|
163
|
+
card to read or edit its Markdown body, filter by status, and switch between the current branch
|
|
164
|
+
and all branches; *复制 Todo* copies the open ones
|
|
145
165
|
(optionally including the done ones, marked ` (done)`) as:
|
|
146
166
|
|
|
147
167
|
```markdown
|
|
@@ -199,7 +219,7 @@ move into the matching scope the first time the file is read.
|
|
|
199
219
|
|
|
200
220
|
Single user, local only. The server binds to `127.0.0.1`, executes git only through
|
|
201
221
|
`execFile('git', [...])` with an argument whitelist (`rev-parse`, `diff`, `show`, `log`, `worktree list`,
|
|
202
|
-
`ls-files`, `status`), refuses option-looking refs and any write-capable flag, and never writes into the
|
|
222
|
+
`ls-files`, `status`, `merge-base`), refuses option-looking refs and any write-capable flag, and never writes into the
|
|
203
223
|
repository. Requests that would need anything else get HTTP 400.
|
|
204
224
|
|
|
205
225
|
## Development
|
package/dist/cli.js
CHANGED
|
@@ -2897,6 +2897,11 @@ function parseSuffix(rest) {
|
|
|
2897
2897
|
if (!isValidRef(sha)) throw new TargetKeyError(`invalid commit ref: ${sha}`);
|
|
2898
2898
|
return { kind: "commit", sha };
|
|
2899
2899
|
}
|
|
2900
|
+
if (rest.startsWith("base:")) {
|
|
2901
|
+
const ref = rest.slice("base:".length);
|
|
2902
|
+
if (!isValidRef(ref)) throw new TargetKeyError(`invalid base ref: ${ref}`);
|
|
2903
|
+
return { kind: "base", ref };
|
|
2904
|
+
}
|
|
2900
2905
|
if (rest.startsWith("range:")) {
|
|
2901
2906
|
const spec = rest.slice("range:".length);
|
|
2902
2907
|
const idx = spec.indexOf("..");
|
|
@@ -2912,7 +2917,7 @@ function parseTargetKey(key) {
|
|
|
2912
2917
|
if (typeof key !== "string" || !key) throw new TargetKeyError("empty target key");
|
|
2913
2918
|
if (key.startsWith("worktree:")) {
|
|
2914
2919
|
const rest = key.slice("worktree:".length);
|
|
2915
|
-
const m = /^(.+):(working|staged|all|commit:.+|range:.+)$/.exec(rest);
|
|
2920
|
+
const m = /^(.+):(working|staged|all|commit:.+|range:.+|base:.+)$/.exec(rest);
|
|
2916
2921
|
if (!m || !m[1] || !m[2]) throw new TargetKeyError(`invalid worktree target key: ${key}`);
|
|
2917
2922
|
const worktree = m[1];
|
|
2918
2923
|
if (!worktree.startsWith("/")) throw new TargetKeyError(`worktree path must be absolute: ${worktree}`);
|
|
@@ -2934,6 +2939,9 @@ function formatTargetKey(t) {
|
|
|
2934
2939
|
case "range":
|
|
2935
2940
|
suffix = `range:${t.base}..${t.head}`;
|
|
2936
2941
|
break;
|
|
2942
|
+
case "base":
|
|
2943
|
+
suffix = `base:${t.ref}`;
|
|
2944
|
+
break;
|
|
2937
2945
|
}
|
|
2938
2946
|
return t.worktree ? `worktree:${t.worktree}:${suffix}` : suffix;
|
|
2939
2947
|
}
|
|
@@ -2980,9 +2988,44 @@ function notFound(message, code = "not_found") {
|
|
|
2980
2988
|
return new HttpError(404, message, code);
|
|
2981
2989
|
}
|
|
2982
2990
|
|
|
2991
|
+
// packages/server/src/commits.ts
|
|
2992
|
+
var SEP = "";
|
|
2993
|
+
var COMMIT_FORMAT = `%H${SEP}%h${SEP}%an${SEP}%ae${SEP}%aI${SEP}%P${SEP}%D${SEP}%s`;
|
|
2994
|
+
function parseDecorations(raw2) {
|
|
2995
|
+
const refs = [];
|
|
2996
|
+
let head = false;
|
|
2997
|
+
for (const part of raw2.split(", ")) {
|
|
2998
|
+
const p = part.trim();
|
|
2999
|
+
if (!p || p === "grafted" || p === "replaced") continue;
|
|
3000
|
+
if (p.endsWith("/HEAD")) continue;
|
|
3001
|
+
if (p === "HEAD") head = true;
|
|
3002
|
+
else if (p.startsWith("HEAD -> ")) {
|
|
3003
|
+
head = true;
|
|
3004
|
+
refs.unshift({ name: p.slice("HEAD -> ".length), kind: "branch" });
|
|
3005
|
+
} else if (p.startsWith("tag: ")) refs.push({ name: p.slice("tag: ".length), kind: "tag" });
|
|
3006
|
+
else refs.push({ name: p, kind: "branch" });
|
|
3007
|
+
}
|
|
3008
|
+
return { refs, head };
|
|
3009
|
+
}
|
|
3010
|
+
function parseCommitLog(stdout) {
|
|
3011
|
+
return stdout.split("\n").filter(Boolean).map((line) => {
|
|
3012
|
+
const [sha = "", shortSha = "", author = "", email = "", date = "", parents = "", decorations = "", ...rest] = line.split(SEP);
|
|
3013
|
+
return {
|
|
3014
|
+
sha,
|
|
3015
|
+
shortSha,
|
|
3016
|
+
author,
|
|
3017
|
+
email,
|
|
3018
|
+
date,
|
|
3019
|
+
parents: parents.split(" ").filter(Boolean),
|
|
3020
|
+
...parseDecorations(decorations),
|
|
3021
|
+
subject: rest.join(SEP)
|
|
3022
|
+
};
|
|
3023
|
+
});
|
|
3024
|
+
}
|
|
3025
|
+
|
|
2983
3026
|
// packages/server/src/git.ts
|
|
2984
3027
|
import { execFile } from "child_process";
|
|
2985
|
-
var ALLOWED_SUBCOMMANDS = /* @__PURE__ */ new Set(["rev-parse", "diff", "show", "log", "worktree", "ls-files", "status"]);
|
|
3028
|
+
var ALLOWED_SUBCOMMANDS = /* @__PURE__ */ new Set(["rev-parse", "diff", "show", "log", "worktree", "ls-files", "status", "merge-base"]);
|
|
2986
3029
|
var FORBIDDEN_OPTION_PREFIXES = ["--output", "--ext-diff", "--textconv", "-c", "--config-env", "--exec-path", "--git-dir", "--work-tree"];
|
|
2987
3030
|
var DEFAULT_GIT_TIMEOUT_MS = 3e4;
|
|
2988
3031
|
var EMPTY_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
@@ -3077,6 +3120,14 @@ async function revParse(cwd, ref) {
|
|
|
3077
3120
|
return void 0;
|
|
3078
3121
|
}
|
|
3079
3122
|
}
|
|
3123
|
+
async function mergeBase(cwd, a, b) {
|
|
3124
|
+
try {
|
|
3125
|
+
const r = await runGit(["merge-base", a, b], { cwd });
|
|
3126
|
+
return r.stdout.trim() || void 0;
|
|
3127
|
+
} catch {
|
|
3128
|
+
return void 0;
|
|
3129
|
+
}
|
|
3130
|
+
}
|
|
3080
3131
|
|
|
3081
3132
|
// packages/server/src/repo.ts
|
|
3082
3133
|
import path from "path";
|
|
@@ -3150,11 +3201,16 @@ async function currentBranch(cwd) {
|
|
|
3150
3201
|
const sha = head.stdout.trim();
|
|
3151
3202
|
return sha ? sha.slice(0, 7) : "HEAD";
|
|
3152
3203
|
}
|
|
3204
|
+
async function detectDefaultBase(cwd) {
|
|
3205
|
+
for (const ref of ["main", "master"]) if (await refExists(cwd, ref)) return ref;
|
|
3206
|
+
return void 0;
|
|
3207
|
+
}
|
|
3153
3208
|
async function getRepoInfo(ctx, defaultTarget) {
|
|
3154
|
-
const [branch, headRes, worktrees] = await Promise.all([
|
|
3209
|
+
const [branch, headRes, worktrees, defaultBase] = await Promise.all([
|
|
3155
3210
|
currentBranch(ctx.root),
|
|
3156
3211
|
runGit(["rev-parse", "HEAD"], { cwd: ctx.root }).catch(() => ({ stdout: "" })),
|
|
3157
|
-
listWorktrees(ctx)
|
|
3212
|
+
listWorktrees(ctx),
|
|
3213
|
+
detectDefaultBase(ctx.root)
|
|
3158
3214
|
]);
|
|
3159
3215
|
return {
|
|
3160
3216
|
root: ctx.root,
|
|
@@ -3162,7 +3218,8 @@ async function getRepoInfo(ctx, defaultTarget) {
|
|
|
3162
3218
|
branch,
|
|
3163
3219
|
head: headRes.stdout.trim(),
|
|
3164
3220
|
worktrees,
|
|
3165
|
-
defaultTarget
|
|
3221
|
+
defaultTarget,
|
|
3222
|
+
...defaultBase ? { defaultBase } : {}
|
|
3166
3223
|
};
|
|
3167
3224
|
}
|
|
3168
3225
|
|
|
@@ -3410,6 +3467,13 @@ function resolveTargetContext(repo, worktrees, key) {
|
|
|
3410
3467
|
async function hasHead(cwd) {
|
|
3411
3468
|
return refExists(cwd, "HEAD");
|
|
3412
3469
|
}
|
|
3470
|
+
async function baseSha(ctx, ref) {
|
|
3471
|
+
if (!await refExists(ctx.cwd, ref)) throw badRequest(`unknown ref: ${ref}`, "unknown_ref");
|
|
3472
|
+
if (!await hasHead(ctx.cwd)) return EMPTY_TREE_SHA;
|
|
3473
|
+
const sha = await mergeBase(ctx.cwd, ref, "HEAD");
|
|
3474
|
+
if (!sha) throw badRequest(`${ref} and HEAD share no history`, "no_merge_base");
|
|
3475
|
+
return sha;
|
|
3476
|
+
}
|
|
3413
3477
|
async function diffArgs(ctx) {
|
|
3414
3478
|
const t = ctx.target;
|
|
3415
3479
|
switch (t.kind) {
|
|
@@ -3429,10 +3493,12 @@ async function diffArgs(ctx) {
|
|
|
3429
3493
|
if (!await refExists(ctx.cwd, t.head)) throw badRequest(`unknown ref: ${t.head}`, "unknown_ref");
|
|
3430
3494
|
return [...DIFF_BASE_ARGS, `${t.base}...${t.head}`];
|
|
3431
3495
|
}
|
|
3496
|
+
case "base":
|
|
3497
|
+
return [...DIFF_BASE_ARGS, await baseSha(ctx, t.ref)];
|
|
3432
3498
|
}
|
|
3433
3499
|
}
|
|
3434
3500
|
function includesUntracked(t) {
|
|
3435
|
-
return t.kind === "working" || t.kind === "all";
|
|
3501
|
+
return t.kind === "working" || t.kind === "all" || t.kind === "base";
|
|
3436
3502
|
}
|
|
3437
3503
|
async function listUntracked(cwd) {
|
|
3438
3504
|
const r = await runGit(["ls-files", "--others", "--exclude-standard", "-z"], { cwd });
|
|
@@ -3512,6 +3578,8 @@ async function refForSide(ctx, side) {
|
|
|
3512
3578
|
return await revParse(ctx.cwd, `${t.sha}^`) ?? EMPTY_TREE_SHA;
|
|
3513
3579
|
case "range":
|
|
3514
3580
|
return side === "new" ? t.head : t.base;
|
|
3581
|
+
case "base":
|
|
3582
|
+
return side === "new" ? void 0 : baseSha(ctx, t.ref);
|
|
3515
3583
|
}
|
|
3516
3584
|
}
|
|
3517
3585
|
async function getFullFile(ctx, filePath, side) {
|
|
@@ -4689,23 +4757,29 @@ function createApp(opts) {
|
|
|
4689
4757
|
api.get("/commits", async (c) => {
|
|
4690
4758
|
const limitRaw = Number(c.req.query("limit") ?? 200);
|
|
4691
4759
|
const limit = Math.min(Math.max(Number.isFinite(limitRaw) ? Math.floor(limitRaw) : 200, 1), 1e3);
|
|
4692
|
-
const
|
|
4760
|
+
const offsetRaw = Number(c.req.query("offset") ?? 0);
|
|
4761
|
+
const offset = Number.isFinite(offsetRaw) && offsetRaw > 0 ? Math.floor(offsetRaw) : 0;
|
|
4693
4762
|
const filePath = c.req.query("path") || void 0;
|
|
4694
4763
|
const ref = c.req.query("ref") || void 0;
|
|
4695
4764
|
const rootParam = c.req.query("root") || void 0;
|
|
4765
|
+
const q = (c.req.query("q") ?? "").trim();
|
|
4766
|
+
const author = (c.req.query("author") ?? "").trim();
|
|
4767
|
+
const firstParent = c.req.query("firstParent") === "1";
|
|
4696
4768
|
let cwd = repo.root;
|
|
4697
4769
|
if (rootParam) {
|
|
4698
4770
|
const wt = (await worktrees()).find((w) => w.path === rootParam);
|
|
4699
4771
|
if (!wt && rootParam !== repo.root) throw badRequest("unknown root", "unknown_worktree");
|
|
4700
4772
|
cwd = rootParam;
|
|
4701
4773
|
}
|
|
4702
|
-
if (before && !isValidRef(before)) throw badRequest("invalid before ref");
|
|
4703
4774
|
if (ref && !isValidRef(ref)) throw badRequest("invalid ref");
|
|
4704
4775
|
if (filePath && (filePath.startsWith("/") || filePath.split("/").includes(".."))) throw badRequest("invalid path");
|
|
4705
|
-
|
|
4706
|
-
const args = ["log", `--max-count=${limit + 1}`, `--
|
|
4707
|
-
if (
|
|
4708
|
-
|
|
4776
|
+
if (q.length > 200 || author.length > 200) throw badRequest("search text too long");
|
|
4777
|
+
const args = ["log", `--max-count=${limit + 1}`, `--skip=${offset}`, `--format=${COMMIT_FORMAT}`];
|
|
4778
|
+
if (firstParent) args.push("--first-parent");
|
|
4779
|
+
if (q || author) args.push("--fixed-strings", "--regexp-ignore-case");
|
|
4780
|
+
if (q) args.push(`--grep=${q}`);
|
|
4781
|
+
if (author) args.push(`--author=${author}`);
|
|
4782
|
+
if (ref) args.push(ref);
|
|
4709
4783
|
if (filePath) args.push("--", filePath);
|
|
4710
4784
|
let stdout = "";
|
|
4711
4785
|
try {
|
|
@@ -4714,13 +4788,17 @@ function createApp(opts) {
|
|
|
4714
4788
|
if (e instanceof HttpError && e.code === "git_failed") stdout = "";
|
|
4715
4789
|
else throw e;
|
|
4716
4790
|
}
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
})
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4791
|
+
const rows = parseCommitLog(stdout);
|
|
4792
|
+
const hasMore = rows.length > limit;
|
|
4793
|
+
let commits = rows.slice(0, limit);
|
|
4794
|
+
if (q && offset === 0 && /^[0-9a-f]{4,40}$/i.test(q)) {
|
|
4795
|
+
const sha = await revParse(cwd, `${q}^{commit}`);
|
|
4796
|
+
if (sha && !commits.some((x) => x.sha === sha)) {
|
|
4797
|
+
const hit = parseCommitLog((await runGit(["log", "--max-count=1", `--format=${COMMIT_FORMAT}`, sha], { cwd })).stdout)[0];
|
|
4798
|
+
if (hit) commits = [hit, ...commits];
|
|
4799
|
+
}
|
|
4800
|
+
}
|
|
4801
|
+
const res = { commits, hasMore };
|
|
4724
4802
|
return c.json(res);
|
|
4725
4803
|
});
|
|
4726
4804
|
api.get("/events", async (c) => {
|
|
@@ -4900,7 +4978,7 @@ async function startServer(opts) {
|
|
|
4900
4978
|
}
|
|
4901
4979
|
|
|
4902
4980
|
// bin/cli.ts
|
|
4903
|
-
var VERSION = true ? "0.
|
|
4981
|
+
var VERSION = true ? "0.5.0" : "dev";
|
|
4904
4982
|
function parseArgs(argv) {
|
|
4905
4983
|
const args = { repoPath: process.cwd(), open: true, help: false, version: false };
|
|
4906
4984
|
for (let i = 0; i < argv.length; i++) {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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;--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:13px/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.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 12px;min-height:42px;background:var(--ground);border-bottom:1px solid var(--line);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}.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}.target{display:flex;align-items:center;gap:8px}.target-label{font-family:var(--mono);font-size:12px;color:var(--muted);max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.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;align-self:stretch;display:flex;align-items:stretch;gap:14px}.tools{display:flex;align-items:center;gap:8px}.nvim{display:inline-flex;align-items:center;gap:4px;max-width:320px}.nvim select{max-width:260px}.nvim-one{font-family:var(--mono);font-size:12px;color:var(--ink-2)}.nvim .muted{font-size:12px}.sidebar{width:280px;min-width:200px;border-right:1px solid var(--line);display:flex;flex-direction:column;background:var(--ground);resize:horizontal;overflow:hidden}.tree-block{display:flex;flex-direction:column;flex:0 1 auto;min-height:66px;min-width:0}.tree-block+.tree-block{border-top:1px solid var(--line)}.block-head{display:flex;align-items:center;gap:8px;padding:8px 10px 4px;font-size:12px;white-space:nowrap}.block-title{font-weight:600}.block-n{font-family:var(--mono);font-size:11px;color:var(--muted)}.block-head .counts{font-family:var(--mono);font-size:11px;display:inline-flex;gap:4px}.block-viewed{font-family:var(--mono);font-size:11px;color:var(--muted)}.block-progress{height:2px;margin:0 10px 4px;background:var(--line);border-radius:1px;overflow:hidden}.block-progress>span{display:block;height:100%;background:var(--ink);transition:width .2s}.tree-tools{display:inline-flex;gap:2px}.tree-tools .link{font-size:11px;color:var(--muted)}.tree{flex:1 1 auto;min-height:0;overflow:auto;padding:2px 0 6px}.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 2px 0 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}.file-head{display:flex;align-items:center;gap:10px;padding:0 12px;min-height:38px;border-bottom:1px solid var(--line);background:var(--surface);flex-wrap:wrap}.file-head .path{font-family:var(--mono);font-size:12px}.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{-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{background:var(--surface-2);color:var(--muted);font-size:11.5px;padding:3px 12px 3px 110px;border-top:1px solid var(--line);border-bottom:1px solid var(--line);white-space:pre-wrap}.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.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}.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.empty{background:var(--surface-2)}.diff-list.split .row.hunk-head{padding-left:60px}.comment-card,.todo-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,.todo-card:hover .card-actions,.todo-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-tools{display:flex;align-items:center;gap:8px;padding:6px 12px;border-bottom:1px solid var(--line);background:var(--ground);flex-wrap:wrap}.commits-tools input{width:140px}.commits-tools input.search{width:260px}.commits-tools input.path{font-size:12px}.commits-count{margin-left:auto;font-size:12px;font-variant-numeric:tabular-nums;white-space:nowrap}.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)}.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}.issues-drawer{width:440px;border-left:1px solid var(--line);display:flex;flex-direction:column;background:var(--ground);min-height:0}.drawer-head{display:flex;align-items:center;gap:10px;padding:0 10px;min-height:36px;border-bottom:1px solid var(--line)}.drawer-title{font-weight:600}.issue-form{display:flex;flex-direction:column;gap:6px;padding:10px;border-bottom:1px solid var(--line);background:var(--surface)}.row-actions{display:flex;gap:6px}.issue-list{flex:1;overflow:auto}.issue-row{display:flex;align-items:center;gap:8px;min-height:34px;padding:4px 10px;border-bottom:1px solid var(--line);cursor:pointer}.issue-row:hover{background:var(--surface-3)}.issue-row .title{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.issue-row .muted{font-size:12px}.issue-detail{flex:1;overflow:auto;padding:12px;background:var(--surface)}.issue-detail h3{margin:8px 0;font-size:15px;font-weight:600;display:flex;align-items:center;gap:8px}.issue-detail-head{display:flex;align-items:center;gap:4px}.section-title{font-weight:600;margin:16px 0 6px;display:flex;align-items:center;gap:8px}.linked-comment{border:1px solid var(--line);border-left:3px solid var(--line-strong);border-radius:var(--r-card);padding:6px 8px;margin-bottom:6px}.linked-comment.status-active{border-left-color:var(--accent)}.linked-comment.status-orphaned{border-color:var(--warn-line);border-left-style:dashed;background:var(--warn-soft)}.linked-head{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:12px}.linked-body{margin-top:4px;white-space:pre-wrap;color:var(--muted);max-height:80px;overflow:hidden}.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)}.rail{width:360px;min-width:300px;border-left:1px solid var(--line);display:flex;flex-direction:column;background:var(--ground);min-height:0}.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}.sidebar.dual{overflow:hidden}.todo-add input{width:100%}.todo-head{display:flex;align-items:flex-start;gap:8px;min-height:20px;cursor:pointer}.todo-head input[type=checkbox]{margin-top:3px}.todo-head .title{flex:1;min-width:0;line-height:20px;overflow-wrap:anywhere}.todo-head .badge{margin-top:1px}.todo-head .time{font-size:11px;line-height:20px;color:var(--muted)}.todo-head .card-actions{position:static;padding-left:0;line-height:20px}.todo-card.status-done{border-left-color:var(--line-strong)}.todo-card.status-done .title{text-decoration:line-through;color:var(--muted)}.todo-body{margin-top:6px;padding-left:21px}.todo-body p:first-child{margin-top:0}.todo-edit{display:flex;flex-direction:column;gap:6px;margin-top:8px}@media(prefers-reduced-motion:reduce){.chev,.card-actions,.block-progress>span{transition:none}}
|