comprehende 0.2.0 → 0.3.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 +16 -2
- package/dist/api/error.js +8 -0
- package/dist/api/live.js +249 -0
- package/dist/api/paths.js +96 -0
- package/dist/api/snapshot.js +45 -0
- package/dist/api/types.js +1 -0
- package/dist/cli/args.js +7 -1
- package/dist/cli/commands.js +6 -0
- package/dist/cli/main.js +22 -10
- package/dist/git/blob.js +21 -0
- package/dist/git/diff.js +44 -2
- package/dist/git/exec.js +29 -3
- package/dist/git/lfs.js +39 -0
- package/dist/schema/image-diff.js +51 -0
- package/dist/schema/image.js +35 -0
- package/dist/server/http.js +83 -198
- package/dist/ui/assets/index-BxIxxAYx.css +1 -0
- package/dist/ui/assets/index-D4bY-beP.js +1598 -0
- package/dist/ui/index.html +3 -2
- package/package.json +1 -1
- package/skills/comprehende/SKILL.md +26 -27
- package/dist/ui/assets/index-BSOLeQ7C.css +0 -1
- package/dist/ui/assets/index-Bgy9jIuw.js +0 -1598
package/dist/git/exec.js
CHANGED
|
@@ -27,7 +27,21 @@ export async function git(cwd, args, opts) {
|
|
|
27
27
|
if (opts?.allowFail) {
|
|
28
28
|
return failure.stdout;
|
|
29
29
|
}
|
|
30
|
-
throw
|
|
30
|
+
throw gitFailure(args, failure);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export async function gitBuffer(cwd, args, opts) {
|
|
34
|
+
try {
|
|
35
|
+
const { stdout } = await execFileAsync("git", ["-c", "core.quotepath=false", ...args], {
|
|
36
|
+
cwd,
|
|
37
|
+
encoding: "buffer",
|
|
38
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
39
|
+
...(opts?.input !== undefined ? { input: Buffer.from(opts.input) } : {}),
|
|
40
|
+
});
|
|
41
|
+
return stdout;
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
throw gitFailure(args, asExecFailure(error));
|
|
31
45
|
}
|
|
32
46
|
}
|
|
33
47
|
export async function gitOk(cwd, args) {
|
|
@@ -44,10 +58,22 @@ function asExecFailure(error) {
|
|
|
44
58
|
const record = error;
|
|
45
59
|
return {
|
|
46
60
|
message: typeof record.message === "string" ? record.message : "git failed",
|
|
47
|
-
stdout:
|
|
48
|
-
stderr:
|
|
61
|
+
stdout: bufferText(record.stdout),
|
|
62
|
+
stderr: bufferText(record.stderr),
|
|
49
63
|
code: typeof record.code === "number" ? record.code : null,
|
|
50
64
|
};
|
|
51
65
|
}
|
|
52
66
|
return { message: "git failed", stdout: "", stderr: "", code: null };
|
|
53
67
|
}
|
|
68
|
+
function bufferText(value) {
|
|
69
|
+
if (typeof value === "string") {
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
if (value instanceof Buffer) {
|
|
73
|
+
return value.toString("utf8");
|
|
74
|
+
}
|
|
75
|
+
return "";
|
|
76
|
+
}
|
|
77
|
+
function gitFailure(args, failure) {
|
|
78
|
+
return new GitError(`git ${args.join(" ")} failed: ${failure.stderr.trim() || failure.message}`, args, failure.stderr, failure.code);
|
|
79
|
+
}
|
package/dist/git/lfs.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
4
|
+
import { LFS_POINTER_VERSION } from "../schema/image.js";
|
|
5
|
+
import { git } from "./exec.js";
|
|
6
|
+
const POINTER_MAX = 1024;
|
|
7
|
+
export function parseLfsPointer(bytes) {
|
|
8
|
+
if (bytes.length === 0 || bytes.length > POINTER_MAX) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
if (bytes.includes(0)) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
const text = new TextDecoder("utf8").decode(bytes);
|
|
15
|
+
if (!text.startsWith(`${LFS_POINTER_VERSION}\n`)) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
const oid = /^oid sha256:([a-f0-9]{64})$/m.exec(text)?.[1];
|
|
19
|
+
const sizeRaw = /^size (\d+)$/m.exec(text)?.[1];
|
|
20
|
+
if (oid === undefined || sizeRaw === undefined) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
return { oid, size: Number(sizeRaw) };
|
|
24
|
+
}
|
|
25
|
+
export function lfsObjectPath(gitCommonDir, oid) {
|
|
26
|
+
return join(gitCommonDir, "lfs", "objects", oid.slice(0, 2), oid.slice(2, 4), oid);
|
|
27
|
+
}
|
|
28
|
+
export async function gitCommonDir(cwd) {
|
|
29
|
+
const raw = (await git(cwd, ["rev-parse", "--git-common-dir"])).trim();
|
|
30
|
+
return isAbsolute(raw) ? raw : resolve(cwd, raw);
|
|
31
|
+
}
|
|
32
|
+
export async function readLfsObject(cwd, pointer) {
|
|
33
|
+
const common = await gitCommonDir(cwd);
|
|
34
|
+
const stored = lfsObjectPath(common, pointer.oid);
|
|
35
|
+
if (!existsSync(stored)) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
return readFile(stored);
|
|
39
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const UNCHANGED_KEEP = 0.35;
|
|
2
|
+
const THRESHOLD = 8;
|
|
3
|
+
/** Paint a same-size RGBA pair. Unchanged pixels go gray; changed pixels go magenta. */
|
|
4
|
+
export function diffRgba(oldPixels, newPixels, width, height, threshold = THRESHOLD) {
|
|
5
|
+
const total = width * height;
|
|
6
|
+
const pixels = new Uint8ClampedArray(total * 4);
|
|
7
|
+
let changed = 0;
|
|
8
|
+
for (let i = 0; i < total; i += 1) {
|
|
9
|
+
const o = i * 4;
|
|
10
|
+
const oldA = oldPixels[o + 3] ?? 0;
|
|
11
|
+
const newA = newPixels[o + 3] ?? 0;
|
|
12
|
+
const empty = oldA === 0 && newA === 0;
|
|
13
|
+
const dr = Math.abs((oldPixels[o] ?? 0) - (newPixels[o] ?? 0));
|
|
14
|
+
const dg = Math.abs((oldPixels[o + 1] ?? 0) - (newPixels[o + 1] ?? 0));
|
|
15
|
+
const db = Math.abs((oldPixels[o + 2] ?? 0) - (newPixels[o + 2] ?? 0));
|
|
16
|
+
const da = Math.abs(oldA - newA);
|
|
17
|
+
const delta = Math.max(dr, dg, db, da);
|
|
18
|
+
if (empty || delta <= threshold) {
|
|
19
|
+
const gray = grayOf(newPixels[o] ?? 0, newPixels[o + 1] ?? 0, newPixels[o + 2] ?? 0);
|
|
20
|
+
const faded = Math.round(gray * UNCHANGED_KEEP);
|
|
21
|
+
pixels[o] = faded;
|
|
22
|
+
pixels[o + 1] = faded;
|
|
23
|
+
pixels[o + 2] = faded;
|
|
24
|
+
pixels[o + 3] = 255;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
changed += 1;
|
|
28
|
+
if (oldA === 0 && newA > 0) {
|
|
29
|
+
pixels[o] = 26;
|
|
30
|
+
pixels[o + 1] = 127;
|
|
31
|
+
pixels[o + 2] = 55;
|
|
32
|
+
pixels[o + 3] = 255;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (newA === 0 && oldA > 0) {
|
|
36
|
+
pixels[o] = 207;
|
|
37
|
+
pixels[o + 1] = 34;
|
|
38
|
+
pixels[o + 2] = 46;
|
|
39
|
+
pixels[o + 3] = 255;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
pixels[o] = 200;
|
|
43
|
+
pixels[o + 1] = 40;
|
|
44
|
+
pixels[o + 2] = 160;
|
|
45
|
+
pixels[o + 3] = 255;
|
|
46
|
+
}
|
|
47
|
+
return { pixels, changed, total };
|
|
48
|
+
}
|
|
49
|
+
function grayOf(r, g, b) {
|
|
50
|
+
return Math.round(0.2126 * r + 0.7152 * g + 0.0722 * b);
|
|
51
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const IMAGE_EXTENSIONS = ["png", "jpg", "jpeg", "gif", "webp", "bmp", "avif", "ico"];
|
|
2
|
+
const IMAGE_EXT = new Set(IMAGE_EXTENSIONS);
|
|
3
|
+
export const LFS_POINTER_VERSION = "version https://git-lfs.github.com/spec/v1";
|
|
4
|
+
const MEDIA = {
|
|
5
|
+
png: "image/png",
|
|
6
|
+
jpg: "image/jpeg",
|
|
7
|
+
jpeg: "image/jpeg",
|
|
8
|
+
gif: "image/gif",
|
|
9
|
+
webp: "image/webp",
|
|
10
|
+
bmp: "image/bmp",
|
|
11
|
+
avif: "image/avif",
|
|
12
|
+
ico: "image/x-icon",
|
|
13
|
+
};
|
|
14
|
+
export function imageExtension(path) {
|
|
15
|
+
const base = path.split("/").pop() ?? path;
|
|
16
|
+
const dot = base.lastIndexOf(".");
|
|
17
|
+
if (dot <= 0) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const ext = base.slice(dot + 1).toLowerCase();
|
|
21
|
+
return IMAGE_EXT.has(ext) ? ext : undefined;
|
|
22
|
+
}
|
|
23
|
+
export function isImagePath(path) {
|
|
24
|
+
return imageExtension(path) !== undefined;
|
|
25
|
+
}
|
|
26
|
+
export function imageMediaType(path) {
|
|
27
|
+
const ext = imageExtension(path);
|
|
28
|
+
return ext === undefined ? "application/octet-stream" : MEDIA[ext];
|
|
29
|
+
}
|
|
30
|
+
export function isLfsPointerText(text) {
|
|
31
|
+
return text.includes(LFS_POINTER_VERSION) && /oid sha256:[a-f0-9]{64}/.test(text);
|
|
32
|
+
}
|
|
33
|
+
export function isImageHunkRef(ref) {
|
|
34
|
+
return ref.oldStart === 0 && ref.oldLines === 0 && ref.newStart === 0 && ref.newLines === 0;
|
|
35
|
+
}
|
package/dist/server/http.js
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
2
|
import { createReadStream, existsSync, statSync } from "node:fs";
|
|
3
3
|
import { extname, join, resolve } from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { blameFile } from "../git/blame.js";
|
|
8
|
-
import { showFile } from "../git/show.js";
|
|
4
|
+
import { ApiError } from "../api/error.js";
|
|
5
|
+
import { openReview, renderResource, snapshotJson } from "../api/live.js";
|
|
6
|
+
import { parseApiPath } from "../api/paths.js";
|
|
9
7
|
import { GitError } from "../git/exec.js";
|
|
10
|
-
import { mergeBase } from "../git/repo.js";
|
|
11
|
-
import { loadDocument } from "../cli/commands.js";
|
|
12
8
|
import { findPackageRoot } from "../package-root.js";
|
|
13
9
|
const MIME = {
|
|
14
10
|
".html": "text/html; charset=utf-8",
|
|
@@ -18,14 +14,22 @@ const MIME = {
|
|
|
18
14
|
".svg": "image/svg+xml",
|
|
19
15
|
".map": "application/json; charset=utf-8",
|
|
20
16
|
".woff2": "font/woff2",
|
|
17
|
+
".png": "image/png",
|
|
18
|
+
".jpg": "image/jpeg",
|
|
19
|
+
".jpeg": "image/jpeg",
|
|
20
|
+
".gif": "image/gif",
|
|
21
|
+
".webp": "image/webp",
|
|
22
|
+
".bmp": "image/bmp",
|
|
23
|
+
".avif": "image/avif",
|
|
24
|
+
".ico": "image/x-icon",
|
|
21
25
|
};
|
|
22
26
|
export async function startServer(opts) {
|
|
23
|
-
const uiRoot = join(findPackageRoot(), "dist/ui");
|
|
27
|
+
const uiRoot = opts.uiRoot ?? join(findPackageRoot(), "dist/ui");
|
|
24
28
|
const server = createServer((req, res) => {
|
|
25
29
|
void handle(req, res, opts, uiRoot);
|
|
26
30
|
});
|
|
27
|
-
await new Promise((
|
|
28
|
-
server.listen(opts.port, "127.0.0.1", () =>
|
|
31
|
+
await new Promise((resolveListen, reject) => {
|
|
32
|
+
server.listen(opts.port, "127.0.0.1", () => resolveListen());
|
|
29
33
|
server.once("error", reject);
|
|
30
34
|
});
|
|
31
35
|
const address = server.address();
|
|
@@ -47,203 +51,36 @@ async function handle(req, res, opts, uiRoot) {
|
|
|
47
51
|
json(res, 200, { ok: true });
|
|
48
52
|
return;
|
|
49
53
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (url.pathname === "/api/hunks") {
|
|
55
|
-
json(res, 200, await hunksPayload(opts, url.searchParams.get("group")));
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
if (url.pathname === "/api/file") {
|
|
59
|
-
json(res, 200, await filePayload(opts, url.searchParams));
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
if (url.pathname === "/api/blame") {
|
|
63
|
-
json(res, 200, await blamePayload(opts, url.searchParams));
|
|
54
|
+
const resource = parseApiPath(url.pathname);
|
|
55
|
+
if (resource !== undefined) {
|
|
56
|
+
const ctx = await openReview(opts.cwd, opts.dataPath);
|
|
57
|
+
sendSnapshot(res, 200, await renderResource(ctx, resource));
|
|
64
58
|
return;
|
|
65
59
|
}
|
|
66
60
|
await serveStatic(res, uiRoot, url.pathname);
|
|
67
61
|
}
|
|
68
62
|
catch (error) {
|
|
69
|
-
const status = error instanceof
|
|
63
|
+
const status = error instanceof ApiError ? error.status : 500;
|
|
70
64
|
const message = error instanceof Error ? error.message : String(error);
|
|
71
65
|
const extra = error instanceof GitError ? { stderr: error.stderr } : {};
|
|
72
66
|
json(res, status, { error: message, ...extra });
|
|
73
67
|
}
|
|
74
68
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
this.status = status;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
async function reviewPayload(opts) {
|
|
83
|
-
const document = await loadDocument(opts.dataPath);
|
|
84
|
-
const resolved = await resolveSource(opts.cwd, document.source.baseRef, document.source.headRef);
|
|
85
|
-
const { files, coverage } = await coverReview(opts.cwd, document);
|
|
86
|
-
const index = await readHunkIndex(opts.cwd, document.source.baseRef, document.source.headRef);
|
|
87
|
-
const commits = await listCommits(opts.cwd, document.source.baseRef, document.source.headRef);
|
|
88
|
-
return {
|
|
89
|
-
document,
|
|
90
|
-
resolved: {
|
|
91
|
-
baseRef: document.source.baseRef,
|
|
92
|
-
headRef: document.source.headRef,
|
|
93
|
-
range: document.source.range ?? `${document.source.baseRef}...${document.source.headRef}`,
|
|
94
|
-
baseSha: resolved.baseSha,
|
|
95
|
-
headSha: resolved.headSha,
|
|
96
|
-
},
|
|
97
|
-
coverage: {
|
|
98
|
-
totalHunks: coverage.totalHunks,
|
|
99
|
-
assignedHunks: coverage.assignedHunks,
|
|
100
|
-
unassignedCount: coverage.unassigned.length,
|
|
101
|
-
staleCount: coverage.stale.length,
|
|
102
|
-
},
|
|
103
|
-
groups: coverage.groups
|
|
104
|
-
.slice()
|
|
105
|
-
.sort((a, b) => a.group.suggestedOrder - b.group.suggestedOrder || a.group.id.localeCompare(b.group.id))
|
|
106
|
-
.map((group) => ({
|
|
107
|
-
id: group.group.id,
|
|
108
|
-
title: group.group.title,
|
|
109
|
-
summary: group.group.summary,
|
|
110
|
-
lookFor: group.group.lookFor ?? [],
|
|
111
|
-
dependsOn: group.group.dependsOn ?? [],
|
|
112
|
-
part: group.group.part,
|
|
113
|
-
suggestedOrder: group.group.suggestedOrder,
|
|
114
|
-
hunkCount: group.hunks.length,
|
|
115
|
-
staleCount: group.stale.length,
|
|
116
|
-
files: uniquePaths(group.hunks),
|
|
117
|
-
})),
|
|
118
|
-
unassigned: {
|
|
119
|
-
hunkCount: coverage.unassigned.length,
|
|
120
|
-
files: uniquePaths(coverage.unassigned),
|
|
121
|
-
},
|
|
122
|
-
stale: coverage.stale,
|
|
123
|
-
files: files.map((file) => ({
|
|
124
|
-
path: file.path,
|
|
125
|
-
oldPath: file.oldPath,
|
|
126
|
-
status: file.status,
|
|
127
|
-
binary: file.binary,
|
|
128
|
-
hunkCount: file.hunks.length,
|
|
129
|
-
})),
|
|
130
|
-
skipped: index.skipped,
|
|
131
|
-
commits,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
async function hunksPayload(opts, groupId) {
|
|
135
|
-
if (groupId === null || groupId === "") {
|
|
136
|
-
throw new HttpError(400, "missing group query");
|
|
137
|
-
}
|
|
138
|
-
const document = await loadDocument(opts.dataPath);
|
|
139
|
-
const { files, coverage } = await coverReview(opts.cwd, document);
|
|
140
|
-
if (groupId === "unassigned") {
|
|
141
|
-
return serializeLayer(files, coverage.unassigned);
|
|
142
|
-
}
|
|
143
|
-
const group = coverage.groups.find((item) => item.group.id === groupId);
|
|
144
|
-
if (group === undefined) {
|
|
145
|
-
throw new HttpError(404, `unknown group "${groupId}"`);
|
|
146
|
-
}
|
|
147
|
-
return serializeLayer(files, group.hunks);
|
|
148
|
-
}
|
|
149
|
-
function serializeLayer(files, hunks) {
|
|
150
|
-
const groups = [];
|
|
151
|
-
for (const hunk of hunks) {
|
|
152
|
-
const existing = groups.find((group) => group.file.path === hunk.path);
|
|
153
|
-
if (existing !== undefined) {
|
|
154
|
-
existing.hunks.push(hunk);
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
const file = files.find((item) => item.path === hunk.path);
|
|
158
|
-
if (file === undefined) {
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
groups.push({ file, hunks: [hunk] });
|
|
69
|
+
function sendSnapshot(res, status, snapshot) {
|
|
70
|
+
if (snapshot.encoding === "json") {
|
|
71
|
+
json(res, status, snapshot.body);
|
|
72
|
+
return;
|
|
162
73
|
}
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
};
|
|
169
|
-
if (file.oldPath !== undefined) {
|
|
170
|
-
next.oldPath = file.oldPath;
|
|
171
|
-
}
|
|
172
|
-
return next;
|
|
74
|
+
const body = Buffer.from(snapshot.body);
|
|
75
|
+
res.writeHead(status, {
|
|
76
|
+
"content-type": snapshot.mediaType,
|
|
77
|
+
"cache-control": "no-store",
|
|
78
|
+
"content-length": body.byteLength,
|
|
173
79
|
});
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
async function filePayload(opts, params) {
|
|
177
|
-
const path = requiredQuery(params, "path");
|
|
178
|
-
const side = params.get("side") === "old" ? "old" : "new";
|
|
179
|
-
const document = await loadDocument(opts.dataPath);
|
|
180
|
-
const { files } = await coverReview(opts.cwd, document);
|
|
181
|
-
const file = findFile(files, path);
|
|
182
|
-
const lookup = side === "old" ? (file.oldPath ?? file.path) : file.path;
|
|
183
|
-
if (side === "old" && file.status === "added") {
|
|
184
|
-
throw new HttpError(404, "file did not exist on the base side");
|
|
185
|
-
}
|
|
186
|
-
if (side === "new" && file.status === "deleted") {
|
|
187
|
-
throw new HttpError(404, "file does not exist on the head side");
|
|
188
|
-
}
|
|
189
|
-
const ref = side === "old" ? await mergeBase(opts.cwd, document.source.baseRef, document.source.headRef) : document.source.headRef;
|
|
190
|
-
try {
|
|
191
|
-
const content = await showFile(opts.cwd, ref, lookup);
|
|
192
|
-
return { path: lookup, ref, side, content, language: fileLanguage(lookup) };
|
|
193
|
-
}
|
|
194
|
-
catch (error) {
|
|
195
|
-
throw new HttpError(404, error instanceof Error ? error.message : "file not found");
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
async function blamePayload(opts, params) {
|
|
199
|
-
const path = requiredQuery(params, "path");
|
|
200
|
-
const side = params.get("side") === "old" ? "old" : "new";
|
|
201
|
-
const document = await loadDocument(opts.dataPath);
|
|
202
|
-
const { files } = await coverReview(opts.cwd, document);
|
|
203
|
-
const file = findFile(files, path);
|
|
204
|
-
const lookup = side === "old" ? (file.oldPath ?? file.path) : file.path;
|
|
205
|
-
if (side === "old" && file.status === "added") {
|
|
206
|
-
throw new HttpError(404, "file did not exist on the base side");
|
|
207
|
-
}
|
|
208
|
-
if (side === "new" && file.status === "deleted") {
|
|
209
|
-
throw new HttpError(404, "file does not exist on the head side");
|
|
210
|
-
}
|
|
211
|
-
const ref = side === "old" ? await mergeBase(opts.cwd, document.source.baseRef, document.source.headRef) : document.source.headRef;
|
|
212
|
-
try {
|
|
213
|
-
const lines = await blameFile(opts.cwd, ref, lookup);
|
|
214
|
-
return { path: lookup, ref, side, lines };
|
|
215
|
-
}
|
|
216
|
-
catch (error) {
|
|
217
|
-
throw new HttpError(404, error instanceof Error ? error.message : "blame not available");
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
function findFile(files, path) {
|
|
221
|
-
const file = files.find((item) => item.path === path || item.oldPath === path);
|
|
222
|
-
if (file === undefined) {
|
|
223
|
-
throw new HttpError(404, `path is not in the live diff: ${path}`);
|
|
224
|
-
}
|
|
225
|
-
return file;
|
|
226
|
-
}
|
|
227
|
-
function requiredQuery(params, name) {
|
|
228
|
-
const value = params.get(name);
|
|
229
|
-
if (value === null || value === "") {
|
|
230
|
-
throw new HttpError(400, `missing ${name}`);
|
|
231
|
-
}
|
|
232
|
-
return value;
|
|
233
|
-
}
|
|
234
|
-
function serializeHunk(hunk) {
|
|
235
|
-
return {
|
|
236
|
-
...toHunkRef(hunk),
|
|
237
|
-
header: hunk.header,
|
|
238
|
-
language: fileLanguage(hunk.path),
|
|
239
|
-
lines: hunk.lines,
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
function uniquePaths(hunks) {
|
|
243
|
-
return [...new Set(hunks.map((hunk) => hunk.path))];
|
|
80
|
+
res.end(body);
|
|
244
81
|
}
|
|
245
82
|
function json(res, status, body) {
|
|
246
|
-
const payload =
|
|
83
|
+
const payload = snapshotJson(body);
|
|
247
84
|
res.writeHead(status, {
|
|
248
85
|
"content-type": "application/json; charset=utf-8",
|
|
249
86
|
"cache-control": "no-store",
|
|
@@ -253,19 +90,67 @@ function json(res, status, body) {
|
|
|
253
90
|
}
|
|
254
91
|
async function serveStatic(res, uiRoot, pathname) {
|
|
255
92
|
if (!existsSync(uiRoot)) {
|
|
256
|
-
throw new
|
|
93
|
+
throw new ApiError(503, "UI is missing from this install. Reinstall comprehende from npm, or from a git checkout run `pnpm build`.");
|
|
257
94
|
}
|
|
258
95
|
const relative = pathname === "/" ? "index.html" : pathname.replace(/^\/+/, "");
|
|
259
96
|
const uiAbs = resolve(uiRoot);
|
|
260
97
|
const candidate = resolve(uiRoot, relative);
|
|
261
98
|
if (candidate !== uiAbs && !candidate.startsWith(`${uiAbs}/`)) {
|
|
262
|
-
throw new
|
|
99
|
+
throw new ApiError(400, "invalid path");
|
|
100
|
+
}
|
|
101
|
+
if (!existsSync(candidate) || !statSync(candidate).isFile()) {
|
|
102
|
+
throw new ApiError(404, "not found");
|
|
263
103
|
}
|
|
264
|
-
const
|
|
265
|
-
const stream = createReadStream(filePath);
|
|
104
|
+
const stream = createReadStream(candidate);
|
|
266
105
|
res.writeHead(200, {
|
|
267
|
-
"content-type": MIME[extname(
|
|
106
|
+
"content-type": MIME[extname(candidate)] ?? "application/octet-stream",
|
|
268
107
|
"cache-control": pathname.startsWith("/assets/") ? "public, max-age=31536000, immutable" : "no-store",
|
|
269
108
|
});
|
|
270
109
|
stream.pipe(res);
|
|
271
110
|
}
|
|
111
|
+
/** Static file server for an exported site. No git. Used by tests to prove the folder is self-contained. */
|
|
112
|
+
export async function startStaticSite(root, port = 0) {
|
|
113
|
+
const server = createServer((req, res) => {
|
|
114
|
+
void (async () => {
|
|
115
|
+
try {
|
|
116
|
+
if (req.method !== "GET") {
|
|
117
|
+
json(res, 405, { error: "method not allowed" });
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const host = req.headers.host ?? "127.0.0.1";
|
|
121
|
+
const url = new URL(req.url ?? "/", `http://${host}`);
|
|
122
|
+
const relative = url.pathname === "/" ? "index.html" : decodeURIComponent(url.pathname.replace(/^\/+/, ""));
|
|
123
|
+
if (relative.split("/").includes("..")) {
|
|
124
|
+
throw new ApiError(400, "invalid path");
|
|
125
|
+
}
|
|
126
|
+
const abs = resolve(root, relative);
|
|
127
|
+
const rootAbs = resolve(root);
|
|
128
|
+
if (abs !== rootAbs && !abs.startsWith(`${rootAbs}/`)) {
|
|
129
|
+
throw new ApiError(400, "invalid path");
|
|
130
|
+
}
|
|
131
|
+
if (!existsSync(abs) || !statSync(abs).isFile()) {
|
|
132
|
+
throw new ApiError(404, "not found");
|
|
133
|
+
}
|
|
134
|
+
const stream = createReadStream(abs);
|
|
135
|
+
res.writeHead(200, {
|
|
136
|
+
"content-type": MIME[extname(abs)] ?? "application/octet-stream",
|
|
137
|
+
});
|
|
138
|
+
stream.pipe(res);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
const status = error instanceof ApiError ? error.status : 500;
|
|
142
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
143
|
+
json(res, status, { error: message });
|
|
144
|
+
}
|
|
145
|
+
})();
|
|
146
|
+
});
|
|
147
|
+
await new Promise((resolveListen, reject) => {
|
|
148
|
+
server.listen(port, "127.0.0.1", () => resolveListen());
|
|
149
|
+
server.once("error", reject);
|
|
150
|
+
});
|
|
151
|
+
const address = server.address();
|
|
152
|
+
if (address === null || typeof address === "string") {
|
|
153
|
+
throw new Error("static site failed to bind 127.0.0.1");
|
|
154
|
+
}
|
|
155
|
+
return { server, port: address.port, url: `http://127.0.0.1:${address.port}` };
|
|
156
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:400;src:url(./ibm-plex-sans-cyrillic-ext-400-normal-g30qAdWV.woff2) format("woff2"),url(./ibm-plex-sans-cyrillic-ext-400-normal-Dsrv2Tcn.woff) format("woff");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:400;src:url(./ibm-plex-sans-cyrillic-400-normal-DZqxrq2p.woff2) format("woff2"),url(./ibm-plex-sans-cyrillic-400-normal-BTotfTJu.woff) format("woff");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:400;src:url(./ibm-plex-sans-greek-400-normal-_efipK4i.woff2) format("woff2"),url(./ibm-plex-sans-greek-400-normal-D9ESIMu3.woff) format("woff");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:400;src:url(./ibm-plex-sans-vietnamese-400-normal-DG4YqDda.woff2) format("woff2"),url(./ibm-plex-sans-vietnamese-400-normal-fK1oJ5dG.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:400;src:url(./ibm-plex-sans-latin-ext-400-normal-C5H60-Va.woff2) format("woff2"),url(./ibm-plex-sans-latin-ext-400-normal-RBey6euL.woff) format("woff");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:400;src:url(./ibm-plex-sans-latin-400-normal-CDDApCn2.woff2) format("woff2"),url(./ibm-plex-sans-latin-400-normal-CYLoc0-x.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:500;src:url(./ibm-plex-sans-cyrillic-ext-500-normal-Cs5J6C77.woff2) format("woff2"),url(./ibm-plex-sans-cyrillic-ext-500-normal-DB5PtV2g.woff) format("woff");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:500;src:url(./ibm-plex-sans-cyrillic-500-normal-CocWQlwt.woff2) format("woff2"),url(./ibm-plex-sans-cyrillic-500-normal-ByOcLdNv.woff) format("woff");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:500;src:url(./ibm-plex-sans-greek-500-normal-JMMifIXV.woff2) format("woff2"),url(./ibm-plex-sans-greek-500-normal-CuWXN6rf.woff) format("woff");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:500;src:url(./ibm-plex-sans-vietnamese-500-normal-e4dixQRQ.woff2) format("woff2"),url(./ibm-plex-sans-vietnamese-500-normal-BEb3_waV.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:500;src:url(./ibm-plex-sans-latin-ext-500-normal-DakdToA3.woff2) format("woff2"),url(./ibm-plex-sans-latin-ext-500-normal-D0aIdm-b.woff) format("woff");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:500;src:url(./ibm-plex-sans-latin-500-normal-6ng42L7E.woff2) format("woff2"),url(./ibm-plex-sans-latin-500-normal-BgVn5rGT.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:600;src:url(./ibm-plex-sans-cyrillic-ext-600-normal-DUMzJB7m.woff2) format("woff2"),url(./ibm-plex-sans-cyrillic-ext-600-normal-Bz0x94Yp.woff) format("woff");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:600;src:url(./ibm-plex-sans-cyrillic-600-normal-71GNu3SW.woff2) format("woff2"),url(./ibm-plex-sans-cyrillic-600-normal-BGq0mW3O.woff) format("woff");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:600;src:url(./ibm-plex-sans-greek-600-normal-DzTrcv_p.woff2) format("woff2"),url(./ibm-plex-sans-greek-600-normal-D-CqTdkO.woff) format("woff");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:600;src:url(./ibm-plex-sans-vietnamese-600-normal-DpPYBSTl.woff2) format("woff2"),url(./ibm-plex-sans-vietnamese-600-normal-DgdngZtN.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:600;src:url(./ibm-plex-sans-latin-ext-600-normal-DOrvGEcy.woff2) format("woff2"),url(./ibm-plex-sans-latin-ext-600-normal-DIrixKbi.woff) format("woff");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:IBM Plex Sans;font-style:normal;font-display:swap;font-weight:600;src:url(./ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2) format("woff2"),url(./ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:IBM Plex Mono;font-style:normal;font-display:swap;font-weight:400;src:url(./ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2) format("woff2"),url(./ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff) format("woff");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:IBM Plex Mono;font-style:normal;font-display:swap;font-weight:400;src:url(./ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2) format("woff2"),url(./ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff) format("woff");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:IBM Plex Mono;font-style:normal;font-display:swap;font-weight:400;src:url(./ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2) format("woff2"),url(./ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:IBM Plex Mono;font-style:normal;font-display:swap;font-weight:400;src:url(./ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2) format("woff2"),url(./ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff) format("woff");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:IBM Plex Mono;font-style:normal;font-display:swap;font-weight:400;src:url(./ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2) format("woff2"),url(./ibm-plex-mono-latin-400-normal-CvHOgSBP.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:IBM Plex Mono;font-style:normal;font-display:swap;font-weight:500;src:url(./ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2) format("woff2"),url(./ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff) format("woff");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:IBM Plex Mono;font-style:normal;font-display:swap;font-weight:500;src:url(./ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2) format("woff2"),url(./ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff) format("woff");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:IBM Plex Mono;font-style:normal;font-display:swap;font-weight:500;src:url(./ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2) format("woff2"),url(./ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:IBM Plex Mono;font-style:normal;font-display:swap;font-weight:500;src:url(./ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2) format("woff2"),url(./ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff) format("woff");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:IBM Plex Mono;font-style:normal;font-display:swap;font-weight:500;src:url(./ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2) format("woff2"),url(./ibm-plex-mono-latin-500-normal-CB9ihrfo.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Serif 4";font-style:normal;font-display:swap;font-weight:400;src:url(./source-serif-4-cyrillic-ext-400-normal-CWV284fw.woff2) format("woff2"),url(./source-serif-4-cyrillic-ext-400-normal-DLkyKLvH.woff) format("woff");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Serif 4";font-style:normal;font-display:swap;font-weight:400;src:url(./source-serif-4-cyrillic-400-normal-C44pakCn.woff2) format("woff2"),url(./source-serif-4-cyrillic-400-normal-B8Z6Jvzv.woff) format("woff");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Serif 4";font-style:normal;font-display:swap;font-weight:400;src:url(./source-serif-4-greek-400-normal-Cw5-AHNN.woff2) format("woff2"),url(./source-serif-4-greek-400-normal-BccYJ-vZ.woff) format("woff");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:"Source Serif 4";font-style:normal;font-display:swap;font-weight:400;src:url(./source-serif-4-vietnamese-400-normal-BbG3LG1A.woff2) format("woff2"),url(./source-serif-4-vietnamese-400-normal-DR1UuFon.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Serif 4";font-style:normal;font-display:swap;font-weight:400;src:url(./source-serif-4-latin-ext-400-normal-Cp7z-ARB.woff2) format("woff2"),url(./source-serif-4-latin-ext-400-normal-CEpydyUl.woff) format("woff");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Serif 4";font-style:normal;font-display:swap;font-weight:400;src:url(./source-serif-4-latin-400-normal-DJ5YJwmz.woff2) format("woff2"),url(./source-serif-4-latin-400-normal-Dn3IlU-Z.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Serif 4";font-style:normal;font-display:swap;font-weight:600;src:url(./source-serif-4-cyrillic-ext-600-normal-D_E8FDgP.woff2) format("woff2"),url(./source-serif-4-cyrillic-ext-600-normal-DiT2IGQm.woff) format("woff");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Serif 4";font-style:normal;font-display:swap;font-weight:600;src:url(./source-serif-4-cyrillic-600-normal-BDP5YDHD.woff2) format("woff2"),url(./source-serif-4-cyrillic-600-normal-DFnC3vAY.woff) format("woff");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:"Source Serif 4";font-style:normal;font-display:swap;font-weight:600;src:url(./source-serif-4-greek-600-normal-DdRkKr-1.woff2) format("woff2"),url(./source-serif-4-greek-600-normal-DhXQw1Kl.woff) format("woff");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:"Source Serif 4";font-style:normal;font-display:swap;font-weight:600;src:url(./source-serif-4-vietnamese-600-normal-CznyfUKR.woff2) format("woff2"),url(./source-serif-4-vietnamese-600-normal-B0WJbuqT.woff) format("woff");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Serif 4";font-style:normal;font-display:swap;font-weight:600;src:url(./source-serif-4-latin-ext-600-normal-0sIUyHMO.woff2) format("woff2"),url(./source-serif-4-latin-ext-600-normal-C6NNqpJh.woff) format("woff");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Serif 4";font-style:normal;font-display:swap;font-weight:600;src:url(./source-serif-4-latin-600-normal-DouSKlru.woff2) format("woff2"),url(./source-serif-4-latin-600-normal-DMD1h6_f.woff) format("woff");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@keyframes wait-walk{0%{top:-40%}to{top:100%}}@media(prefers-reduced-motion:no-preference){.wait-gutter-walker{animation:wait-walk 1.7s var(--motion-ease) infinite}}@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-duration:initial;--tw-content:"";--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0}}}@layer theme{:root,:host{--font-sans:"IBM Plex Sans", ui-sans-serif, system-ui, sans-serif;--font-mono:"IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--font-weight-normal:400;--font-weight-medium:500;--tracking-wide:.025em;--leading-snug:1.375;--leading-relaxed:1.625;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:"IBM Plex Sans", ui-sans-serif, system-ui, sans-serif;--default-mono-font-family:"IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*{border-color:var(--border)}html,body,#root{height:100%}html{view-transition-name:none}body{background-color:var(--background);color:var(--foreground);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:IBM Plex Sans,ui-sans-serif,system-ui,sans-serif;font-size:14px;line-height:1.55}code,pre,kbd{font-family:var(--font-mono)}.image-stage{background-color:var(--diff-canvas);background-image:repeating-conic-gradient(var(--muted) 0% 25%,var(--diff-canvas) 0% 50%);box-shadow:inset 0 0 0 1px var(--border);background-size:12px 12px}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-x-0{inset-inline:0}.inset-y-0{inset-block:0}.top-0{top:0}.top-1\/2{top:50%}.top-\[28\%\]{top:28%}.left-0{left:0}.z-1{z-index:1}.z-10{z-index:10}.z-50{z-index:50}.m-0{margin:0}.mx-1{margin-inline:var(--spacing)}.mx-3{margin-inline:calc(var(--spacing) * 3)}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-8{margin-top:calc(var(--spacing) * 8)}.mt-12{margin-top:calc(var(--spacing) * 12)}.mt-px{margin-top:1px}.mb-1{margin-bottom:var(--spacing)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.mb-10{margin-bottom:calc(var(--spacing) * 10)}.ml-auto{margin-left:auto}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.size-2{width:calc(var(--spacing) * 2);height:calc(var(--spacing) * 2)}.size-2\.5{width:calc(var(--spacing) * 2.5);height:calc(var(--spacing) * 2.5)}.size-3\.5{width:calc(var(--spacing) * 3.5);height:calc(var(--spacing) * 3.5)}.size-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.size-6{width:calc(var(--spacing) * 6);height:calc(var(--spacing) * 6)}.size-8{width:calc(var(--spacing) * 8);height:calc(var(--spacing) * 8)}.size-9{width:calc(var(--spacing) * 9);height:calc(var(--spacing) * 9)}.size-full{width:100%;height:100%}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-10{height:calc(var(--spacing) * 10)}.h-\[1\.1rem\]{height:1.1rem}.h-\[44\%\]{height:44%}.h-auto{height:auto}.h-full{height:100%}.min-h-0{min-height:0}.w-0\.5{width:calc(var(--spacing) * .5)}.w-2{width:calc(var(--spacing) * 2)}.w-3{width:calc(var(--spacing) * 3)}.w-5{width:calc(var(--spacing) * 5)}.w-\[3px\]{width:3px}.w-\[calc\(100\%-24px\)\]{width:calc(100% - 24px)}.w-fit{width:fit-content}.w-full{width:100%}.w-px{width:1px}.max-w-\[68ch\]{max-width:68ch}.max-w-full{max-width:100%}.max-w-none{max-width:none}.min-w-0{min-width:0}.min-w-8{min-width:calc(var(--spacing) * 8)}.min-w-9{min-width:calc(var(--spacing) * 9)}.min-w-10{min-width:calc(var(--spacing) * 10)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.origin-\(--radix-tooltip-content-transform-origin\){transform-origin:var(--radix-tooltip-content-transform-origin)}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.-rotate-90{rotate:-90deg}.cursor-col-resize{cursor:col-resize}.cursor-pointer{cursor:pointer}.touch-none{touch-action:none}.resize{resize:both}.list-disc{list-style-type:disc}.list-none{list-style-type:none}.auto-cols-\[minmax\(16rem\,1fr\)\]{grid-auto-columns:minmax(16rem,1fr)}.grid-flow-col{grid-auto-flow:column}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-center{justify-content:center}.justify-start{justify-content:flex-start}.gap-0{gap:0}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-6{gap:calc(var(--spacing) * 6)}.gap-\[0\.28rem\]{gap:.28rem}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-y-1{row-gap:var(--spacing)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.rounded-\[1px\]{border-radius:1px}.rounded-\[inherit\]{border-radius:inherit}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:calc(var(--radius) * 1.3)}.rounded-md{border-radius:var(--radius)}.rounded-none{border-radius:0}.rounded-sm{border-radius:calc(var(--radius) * .7)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-border{border-color:var(--border)}.border-input{border-color:var(--input)}.border-primary{border-color:var(--primary)}.border-transparent{border-color:#0000}.bg-\[var\(--diff-canvas\)\]{background-color:var(--diff-canvas)}.bg-add,.bg-add\/38{background-color:var(--add)}@supports (color:color-mix(in lab,red,red)){.bg-add\/38{background-color:color-mix(in oklab,var(--add) 38%,transparent)}}.bg-background{background-color:var(--background)}.bg-border{background-color:var(--border)}.bg-card{background-color:var(--card)}.bg-del,.bg-del\/38{background-color:var(--del)}@supports (color:color-mix(in lab,red,red)){.bg-del\/38{background-color:color-mix(in oklab,var(--del) 38%,transparent)}}.bg-destructive,.bg-destructive\/20{background-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.bg-destructive\/20{background-color:color-mix(in oklab,var(--destructive) 20%,transparent)}}.bg-foreground{background-color:var(--foreground)}.bg-primary{background-color:var(--primary)}.bg-secondary{background-color:var(--secondary)}.bg-transparent{background-color:#0000}.p-0{padding:0}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.px-8{padding-inline:calc(var(--spacing) * 8)}.px-10{padding-inline:calc(var(--spacing) * 10)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-8{padding-block:calc(var(--spacing) * 8)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-6{padding-top:calc(var(--spacing) * 6)}.pb-1{padding-bottom:var(--spacing)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pb-8{padding-bottom:calc(var(--spacing) * 8)}.pl-5{padding-left:calc(var(--spacing) * 5)}.text-left{text-align:left}.font-mono{font-family:IBM Plex Mono,ui-monospace,SF Mono,Menlo,monospace}.font-sans{font-family:IBM Plex Sans,ui-sans-serif,system-ui,sans-serif}.font-serif{font-family:"Source Serif 4",Iowan Old Style,Georgia,serif}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[1\.75rem\]{font-size:1.75rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.text-add{color:var(--add)}.text-background{color:var(--background)}.text-del{color:var(--del)}.text-destructive{color:var(--destructive)}.text-foreground{color:var(--foreground)}.text-muted-foreground{color:var(--muted-foreground)}.text-primary{color:var(--primary)}.text-primary-foreground{color:var(--primary-foreground)}.text-secondary-foreground{color:var(--secondary-foreground)}.text-warn{color:var(--warn)}.text-white{color:var(--color-white)}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.underline-offset-4{text-underline-offset:4px}.accent-primary{accent-color:var(--primary)}.opacity-45{opacity:.45}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-150{--tw-duration:.15s;transition-duration:.15s}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:inset-y-0:after{content:var(--tw-content);inset-block:0}.after\:left-1\/2:after{content:var(--tw-content);left:50%}.after\:w-px:after{content:var(--tw-content);width:1px}.after\:-translate-x-1\/2:after{content:var(--tw-content);--tw-translate-x: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.after\:bg-border:after{content:var(--tw-content);background-color:var(--border)}.last\:mb-0:last-child{margin-bottom:0}@media(hover:hover){.hover\:bg-accent:hover{background-color:var(--accent)}.hover\:bg-destructive\/90:hover{background-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-destructive\/90:hover{background-color:color-mix(in oklab,var(--destructive) 90%,transparent)}}.hover\:bg-muted:hover{background-color:var(--muted)}.hover\:bg-primary\/90:hover{background-color:var(--primary)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-primary\/90:hover{background-color:color-mix(in oklab,var(--primary) 90%,transparent)}}.hover\:bg-secondary\/80:hover{background-color:var(--secondary)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-secondary\/80:hover{background-color:color-mix(in oklab,var(--secondary) 80%,transparent)}}.hover\:bg-transparent:hover{background-color:#0000}.hover\:text-accent-foreground:hover{color:var(--accent-foreground)}.hover\:text-muted-foreground:hover{color:var(--muted-foreground)}.hover\:text-warn:hover{color:var(--warn)}.hover\:underline:hover{text-decoration-line:underline}.hover\:after\:bg-primary\/60:hover:after{content:var(--tw-content);background-color:var(--primary)}@supports (color:color-mix(in lab,red,red)){.hover\:after\:bg-primary\/60:hover:after{background-color:color-mix(in oklab,var(--primary) 60%,transparent)}}.hover\:after\:bg-primary\/70:hover:after{content:var(--tw-content);background-color:var(--primary)}@supports (color:color-mix(in lab,red,red)){.hover\:after\:bg-primary\/70:hover:after{background-color:color-mix(in oklab,var(--primary) 70%,transparent)}}}.focus-visible\:border-ring:focus-visible{border-color:var(--ring)}.focus-visible\:ring-1:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:ring-ring:focus-visible,.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:var(--ring)}@supports (color:color-mix(in lab,red,red)){.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:color-mix(in oklab, var(--ring) 50%, transparent)}}.focus-visible\:outline-hidden:focus-visible{--tw-outline-style:none;outline-style:none}@media(forced-colors:active){.focus-visible\:outline-hidden:focus-visible{outline-offset:2px;outline:2px solid #0000}}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:opacity-50:disabled{opacity:.5}.has-\[\>svg\]\:px-2\.5:has(>svg){padding-inline:calc(var(--spacing) * 2.5)}.has-\[\>svg\]\:px-3:has(>svg){padding-inline:calc(var(--spacing) * 3)}.has-\[\>svg\]\:px-4:has(>svg){padding-inline:calc(var(--spacing) * 4)}.aria-\[orientation\=horizontal\]\:h-2[aria-orientation=horizontal]{height:calc(var(--spacing) * 2)}.aria-\[orientation\=horizontal\]\:w-full[aria-orientation=horizontal]{width:100%}.aria-\[orientation\=horizontal\]\:after\:left-0[aria-orientation=horizontal]:after{content:var(--tw-content);left:0}.aria-\[orientation\=horizontal\]\:after\:h-px[aria-orientation=horizontal]:after{content:var(--tw-content);height:1px}.aria-\[orientation\=horizontal\]\:after\:w-full[aria-orientation=horizontal]:after{content:var(--tw-content);width:100%}.aria-\[orientation\=horizontal\]\:after\:translate-x-0[aria-orientation=horizontal]:after{content:var(--tw-content);--tw-translate-x:0px;translate:var(--tw-translate-x) var(--tw-translate-y)}.aria-\[orientation\=horizontal\]\:after\:-translate-y-1\/2[aria-orientation=horizontal]:after{content:var(--tw-content);--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.aria-\[orientation\=vertical\]\:flex-col[aria-orientation=vertical]{flex-direction:column}.data-\[orientation\=horizontal\]\:h-px[data-orientation=horizontal]{height:1px}.data-\[orientation\=horizontal\]\:w-full[data-orientation=horizontal]{width:100%}.data-\[orientation\=vertical\]\:h-full[data-orientation=vertical]{height:100%}.data-\[orientation\=vertical\]\:w-px[data-orientation=vertical]{width:1px}.data-\[state\=closed\]\:animate-out[data-state=closed]{animation:exit var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.data-\[state\=closed\]\:fade-out-0[data-state=closed]{--tw-exit-opacity:0}.data-\[state\=delayed-open\]\:animate-in[data-state=delayed-open]{animation:enter var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.data-\[state\=delayed-open\]\:fade-in-0[data-state=delayed-open]{--tw-enter-opacity:0}.data-\[state\=instant-open\]\:animate-in[data-state=instant-open]{animation:enter var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.data-\[state\=instant-open\]\:fade-in-0[data-state=instant-open]{--tw-enter-opacity:0}.data-\[state\=on\]\:bg-accent[data-state=on]{background-color:var(--accent)}.data-\[state\=on\]\:text-accent-foreground[data-state=on]{color:var(--accent-foreground)}@media(prefers-reduced-motion:no-preference){.motion-safe\:after\:transition-colors:after{content:var(--tw-content);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-4 svg:not([class*=size-]){width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.\[\&\>svg\]\:pointer-events-none>svg{pointer-events:none}.\[\&\>svg\]\:size-3>svg{width:calc(var(--spacing) * 3);height:calc(var(--spacing) * 3)}}@property --tw-animation-delay{syntax:"*";inherits:false;initial-value:0s}@property --tw-animation-direction{syntax:"*";inherits:false;initial-value:normal}@property --tw-animation-duration{syntax:"*";inherits:false}@property --tw-animation-fill-mode{syntax:"*";inherits:false;initial-value:none}@property --tw-animation-iteration-count{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-translate-y{syntax:"*";inherits:false;initial-value:0}:root{color-scheme:light;--motion:.18s;--motion-brief:.5s;--motion-brief-gap:50ms;--motion-ease:cubic-bezier(.2, .8, .2, 1);--radius:.45rem;--background:#f4f6f8;--foreground:#171b22;--card:#fff;--card-foreground:#171b22;--popover:#fff;--popover-foreground:#171b22;--primary:#3d4fd8;--primary-foreground:#fff;--secondary:#e8ecf1;--secondary-foreground:#171b22;--muted:#e8ecf1;--muted-foreground:#5c6570;--accent:#e4e8f4;--accent-foreground:#171b22;--destructive:#cf222e;--border:#d5dbe3;--input:#d5dbe3;--ring:#3d4fd8;--add:#1a7f37;--del:#cf222e;--warn:#9a6700;--add-bg:#1a7f3729;--del-bg:#cf222e29;--diff-canvas:#fff;--diff-fg:#1f2328;--diff-add-line:#e6ffec;--diff-add-num:#ccffd8;--diff-add-word:#abf2bc;--diff-del-line:#ffebe9;--diff-del-num:#ffd7d5;--diff-del-word:#ff818266;--diff-hunk:#ddf4ff;--diff-hover:#f6f8fa;--strand-0:#2458b5;--strand-1:#9a5b00;--strand-2:#6b3d96;--strand-3:#0b6e78;--strand-4:#9a356e;--strand-5:#3a5366}.dark{color-scheme:dark;--background:#12151c;--foreground:#e6eaf0;--card:#1a1e27;--card-foreground:#e6eaf0;--popover:#1a1e27;--popover-foreground:#e6eaf0;--primary:#8b9cff;--primary-foreground:#12151c;--secondary:#252a33;--secondary-foreground:#e6eaf0;--muted:#252a33;--muted-foreground:#8b939e;--accent:#2a3148;--accent-foreground:#e6eaf0;--destructive:#f85149;--border:#2c3340;--input:#2c3340;--ring:#8b9cff;--add:#3fb950;--del:#f85149;--warn:#d4a017;--add-bg:#3fb95029;--del-bg:#f8514929;--diff-canvas:#0d1117;--diff-fg:#e6edf3;--diff-add-line:#2ea04326;--diff-add-num:#2ea04366;--diff-add-word:#2ea04366;--diff-del-line:#f8514926;--diff-del-num:#f8514966;--diff-del-word:#f8514966;--diff-hunk:#388bfd1a;--diff-hover:#161b22;--strand-0:#8fb4f0;--strand-1:#e0a64a;--strand-2:#c9a3e8;--strand-3:#6ed0d6;--strand-4:#e399c0;--strand-5:#9ab0c2}@media(prefers-reduced-motion:no-preference){[data-slot=button],[data-slot=toggle],[data-slot=badge]{transition-property:color,background-color,border-color,box-shadow,opacity;transition-duration:var(--motion);transition-timing-function:var(--motion-ease)}.review-main{scroll-behavior:smooth}.hunk-card{transition:border-color var(--motion) var(--motion-ease)}.hunk-chevron{transition:transform var(--motion) var(--motion-ease)}.file-path{transition:color var(--motion) var(--motion-ease)}.stack-strand{transition:opacity var(--motion) var(--motion-ease)}.stack-selection{transition-property:top,left,width,height;transition-duration:var(--motion);transition-timing-function:var(--motion-ease)}[data-slot=resizable-handle]:after{transition:background-color var(--motion) var(--motion-ease)}}html[data-motion=layer] .review-brief-strand{view-transition-name:review-strand}html[data-motion=layer] .review-brief-copy{view-transition-name:review-copy}html[data-motion=layer] .review-overview{view-transition-name:review-overview}html[data-motion=scene] .review-scene{view-transition-name:review-scene}.review-brief{align-items:stretch;gap:1rem;margin-bottom:2rem;display:flex}.review-brief-strand{border-radius:1px;flex:none;width:3px}.review-brief-copy{flex:1;min-width:0}::view-transition-old(review-strand){mix-blend-mode:normal;animation-timing-function:var(--motion-ease);animation-fill-mode:both}::view-transition-new(review-strand){mix-blend-mode:normal;animation-timing-function:var(--motion-ease);animation-fill-mode:both}::view-transition-old(review-copy){mix-blend-mode:normal;animation-timing-function:var(--motion-ease);animation-fill-mode:both}::view-transition-new(review-copy){mix-blend-mode:normal;animation-timing-function:var(--motion-ease);animation-fill-mode:both}::view-transition-old(review-overview){mix-blend-mode:normal;animation-timing-function:var(--motion-ease);animation-fill-mode:both}::view-transition-new(review-overview){mix-blend-mode:normal;animation-timing-function:var(--motion-ease);animation-fill-mode:both}::view-transition-old(review-scene){mix-blend-mode:normal;animation-timing-function:var(--motion-ease);animation-fill-mode:both}::view-transition-new(review-scene){mix-blend-mode:normal;animation-timing-function:var(--motion-ease);animation-fill-mode:both}::view-transition-old(review-strand){height:100%;animation-duration:var(--motion-brief);overflow:clip}::view-transition-new(review-strand){height:100%;animation-duration:var(--motion-brief);overflow:clip}::view-transition-old(review-strand){animation-name:motion-fade-out}::view-transition-new(review-strand){animation-name:motion-fade-in}::view-transition-group(review-strand){animation-duration:var(--motion-brief);animation-timing-function:var(--motion-ease)}::view-transition-image-pair(review-strand){isolation:isolate}::view-transition-old(review-copy){animation-name:motion-slide-down-out;animation-duration:calc((var(--motion-brief) - var(--motion-brief-gap)) / 2)}::view-transition-new(review-copy){animation-name:motion-slide-up-in;animation-duration:calc((var(--motion-brief) - var(--motion-brief-gap)) / 2);animation-delay:calc((var(--motion-brief) + var(--motion-brief-gap)) / 2)}::view-transition-group(review-copy){animation:none}::view-transition-old(review-overview){animation-name:motion-fade-out;animation-duration:var(--motion-brief)}::view-transition-new(review-overview){animation-name:motion-fade-in;animation-duration:var(--motion-brief)}::view-transition-group(review-overview){animation:none}::view-transition-old(review-scene){animation-name:motion-fade-out;animation-duration:var(--motion)}::view-transition-new(review-scene){animation-name:motion-fade-in;animation-duration:var(--motion)}::view-transition-group(review-scene){animation-duration:var(--motion);animation-timing-function:var(--motion-ease)}::view-transition-image-pair(review-scene){isolation:isolate}.stack{position:relative}.stack-item-active{background-color:var(--accent)}.stack-selection{display:none}.stack-strand{pointer-events:none;z-index:1;background-color:var(--strand,var(--primary));border-radius:9999px;width:.125rem;position:absolute;inset:.375rem auto .375rem 0}.stack-item:hover:not(.stack-item-active){background-color:var(--accent)}@supports (anchor-name:--stack-active){.stack-item-active{anchor-name:--stack-active;background-color:#0000}.stack-selection{pointer-events:none;z-index:0;position-anchor:--stack-active;top:anchor(top);left:anchor(left);width:anchor-size(width);height:anchor-size(height);border-radius:var(--radius);background-color:var(--accent);display:block;position:absolute}}@keyframes motion-fade-out{to{opacity:0}}@keyframes motion-fade-in{0%{opacity:0}}@keyframes motion-slide-down-out{to{opacity:0;translate:0 .75rem}}@keyframes motion-slide-up-in{0%{opacity:0;translate:0 .75rem}}@media(prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}::view-transition-group(*){animation:none!important}::view-transition-old(*){animation:none!important}::view-transition-new(*){animation:none!important}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-duration{syntax:"*";inherits:false}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@keyframes enter{0%{opacity:var(--tw-enter-opacity,1);transform:translate3d(var(--tw-enter-translate-x,0),var(--tw-enter-translate-y,0),0)scale3d(var(--tw-enter-scale,1),var(--tw-enter-scale,1),var(--tw-enter-scale,1))rotate(var(--tw-enter-rotate,0));filter:blur(var(--tw-enter-blur,0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity,1);transform:translate3d(var(--tw-exit-translate-x,0),var(--tw-exit-translate-y,0),0)scale3d(var(--tw-exit-scale,1),var(--tw-exit-scale,1),var(--tw-exit-scale,1))rotate(var(--tw-exit-rotate,0));filter:blur(var(--tw-exit-blur,0))}}
|