glassbox 0.3.6 → 0.4.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/dist/cli.js +295 -63
- package/dist/client/app.global.js +9 -9
- package/dist/client/styles.css +1 -1
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -391,7 +391,7 @@ init_queries();
|
|
|
391
391
|
init_connection();
|
|
392
392
|
import { mkdirSync as mkdirSync7 } from "fs";
|
|
393
393
|
import { tmpdir } from "os";
|
|
394
|
-
import { join as
|
|
394
|
+
import { join as join10, resolve as resolve4 } from "path";
|
|
395
395
|
|
|
396
396
|
// src/debug.ts
|
|
397
397
|
var debugEnabled = false;
|
|
@@ -1717,9 +1717,9 @@ async function updateReviewDiffs(reviewId, newDiffs, headCommit) {
|
|
|
1717
1717
|
// src/server.ts
|
|
1718
1718
|
import { serve } from "@hono/node-server";
|
|
1719
1719
|
import { exec } from "child_process";
|
|
1720
|
-
import { existsSync as
|
|
1720
|
+
import { existsSync as existsSync6, readFileSync as readFileSync8 } from "fs";
|
|
1721
1721
|
import { Hono as Hono4 } from "hono";
|
|
1722
|
-
import { dirname, join as
|
|
1722
|
+
import { dirname, join as join7 } from "path";
|
|
1723
1723
|
import { fileURLToPath } from "url";
|
|
1724
1724
|
|
|
1725
1725
|
// src/routes/ai-api.ts
|
|
@@ -2439,8 +2439,8 @@ function isRetriable(err) {
|
|
|
2439
2439
|
return msg.includes("429") || msg.includes("500") || msg.includes("502") || msg.includes("503") || msg.includes("504") || msg.includes("rate_limit");
|
|
2440
2440
|
}
|
|
2441
2441
|
function sleep(ms) {
|
|
2442
|
-
return new Promise((
|
|
2443
|
-
setTimeout(
|
|
2442
|
+
return new Promise((resolve5) => {
|
|
2443
|
+
setTimeout(resolve5, ms);
|
|
2444
2444
|
});
|
|
2445
2445
|
}
|
|
2446
2446
|
|
|
@@ -2484,8 +2484,8 @@ function randomLines(count) {
|
|
|
2484
2484
|
return lines.sort((a, b) => a.line - b.line);
|
|
2485
2485
|
}
|
|
2486
2486
|
function sleep2(ms) {
|
|
2487
|
-
return new Promise((
|
|
2488
|
-
setTimeout(
|
|
2487
|
+
return new Promise((resolve5) => {
|
|
2488
|
+
setTimeout(resolve5, ms);
|
|
2489
2489
|
});
|
|
2490
2490
|
}
|
|
2491
2491
|
async function mockRiskAnalysisBatch(files) {
|
|
@@ -2937,9 +2937,10 @@ aiApiRoutes.post("/preferences", async (c) => {
|
|
|
2937
2937
|
|
|
2938
2938
|
// src/routes/api.ts
|
|
2939
2939
|
init_queries();
|
|
2940
|
-
import {
|
|
2940
|
+
import { execSync as execSync5 } from "child_process";
|
|
2941
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
2941
2942
|
import { Hono as Hono2 } from "hono";
|
|
2942
|
-
import { join as
|
|
2943
|
+
import { join as join6, resolve as resolve3 } from "path";
|
|
2943
2944
|
|
|
2944
2945
|
// src/export/generate.ts
|
|
2945
2946
|
init_queries();
|
|
@@ -3103,6 +3104,9 @@ function isImageFile(filePath) {
|
|
|
3103
3104
|
const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
|
|
3104
3105
|
return IMAGE_EXTENSIONS.has(ext);
|
|
3105
3106
|
}
|
|
3107
|
+
function isSvgFile(filePath) {
|
|
3108
|
+
return filePath.slice(filePath.lastIndexOf(".")).toLowerCase() === ".svg";
|
|
3109
|
+
}
|
|
3106
3110
|
function getContentType(filePath) {
|
|
3107
3111
|
const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
|
|
3108
3112
|
switch (ext) {
|
|
@@ -3397,6 +3401,139 @@ function parseWebp(data) {
|
|
|
3397
3401
|
return { format: "webp", width, height, colorSpace: "srgb", channels: hasAlpha ? 4 : 3, depth: null, hasAlpha, density: null, exif: null };
|
|
3398
3402
|
}
|
|
3399
3403
|
|
|
3404
|
+
// src/git/svg-rasterize.ts
|
|
3405
|
+
import { existsSync as existsSync4, readFileSync as readFileSync6 } from "fs";
|
|
3406
|
+
import { createRequire } from "module";
|
|
3407
|
+
import { join as join5 } from "path";
|
|
3408
|
+
var initialized = false;
|
|
3409
|
+
var ResvgClass;
|
|
3410
|
+
var fontBuffers = [];
|
|
3411
|
+
async function ensureInit() {
|
|
3412
|
+
if (initialized) return;
|
|
3413
|
+
const require2 = createRequire(import.meta.url);
|
|
3414
|
+
const resvgPath = require2.resolve("@resvg/resvg-wasm");
|
|
3415
|
+
const wasmPath = resvgPath.replace(/index\.(js|mjs)$/, "index_bg.wasm");
|
|
3416
|
+
const wasmBuffer = readFileSync6(wasmPath);
|
|
3417
|
+
const mod = await import("@resvg/resvg-wasm");
|
|
3418
|
+
await mod.initWasm(wasmBuffer);
|
|
3419
|
+
ResvgClass = mod.Resvg;
|
|
3420
|
+
fontBuffers = loadSystemFonts();
|
|
3421
|
+
initialized = true;
|
|
3422
|
+
}
|
|
3423
|
+
function loadSystemFonts() {
|
|
3424
|
+
const buffers = [];
|
|
3425
|
+
const candidates = getFontCandidates();
|
|
3426
|
+
for (const path of candidates) {
|
|
3427
|
+
if (!existsSync4(path)) continue;
|
|
3428
|
+
try {
|
|
3429
|
+
buffers.push(readFileSync6(path));
|
|
3430
|
+
} catch {
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3433
|
+
return buffers;
|
|
3434
|
+
}
|
|
3435
|
+
function getFontCandidates() {
|
|
3436
|
+
switch (process.platform) {
|
|
3437
|
+
case "darwin": {
|
|
3438
|
+
const sys = "/System/Library/Fonts";
|
|
3439
|
+
const sup = "/System/Library/Fonts/Supplemental";
|
|
3440
|
+
return [
|
|
3441
|
+
// Core system fonts (serif, sans-serif, monospace)
|
|
3442
|
+
join5(sys, "Helvetica.ttc"),
|
|
3443
|
+
join5(sys, "Times.ttc"),
|
|
3444
|
+
join5(sys, "Courier.ttc"),
|
|
3445
|
+
join5(sys, "Menlo.ttc"),
|
|
3446
|
+
join5(sys, "SFPro.ttf"),
|
|
3447
|
+
join5(sys, "SFNS.ttf"),
|
|
3448
|
+
join5(sys, "SFNSMono.ttf"),
|
|
3449
|
+
// Supplemental (common named fonts in SVGs)
|
|
3450
|
+
join5(sup, "Arial.ttf"),
|
|
3451
|
+
join5(sup, "Arial Bold.ttf"),
|
|
3452
|
+
join5(sup, "Georgia.ttf"),
|
|
3453
|
+
join5(sup, "Verdana.ttf"),
|
|
3454
|
+
join5(sup, "Tahoma.ttf"),
|
|
3455
|
+
join5(sup, "Trebuchet MS.ttf"),
|
|
3456
|
+
join5(sup, "Impact.ttf"),
|
|
3457
|
+
join5(sup, "Comic Sans MS.ttf"),
|
|
3458
|
+
join5(sup, "Courier New.ttf"),
|
|
3459
|
+
join5(sup, "Times New Roman.ttf")
|
|
3460
|
+
];
|
|
3461
|
+
}
|
|
3462
|
+
case "linux":
|
|
3463
|
+
return [
|
|
3464
|
+
// DejaVu (most common Linux fallback)
|
|
3465
|
+
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
|
3466
|
+
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
|
|
3467
|
+
"/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf",
|
|
3468
|
+
"/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
|
|
3469
|
+
// Liberation (metric-compatible with Arial/Times/Courier)
|
|
3470
|
+
"/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
|
|
3471
|
+
"/usr/share/fonts/truetype/liberation/LiberationSerif-Regular.ttf",
|
|
3472
|
+
"/usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf",
|
|
3473
|
+
// Noto (common on modern distros)
|
|
3474
|
+
"/usr/share/fonts/truetype/noto/NotoSans-Regular.ttf"
|
|
3475
|
+
];
|
|
3476
|
+
case "win32": {
|
|
3477
|
+
const winFonts = join5(process.env.WINDIR ?? "C:\\Windows", "Fonts");
|
|
3478
|
+
return [
|
|
3479
|
+
join5(winFonts, "arial.ttf"),
|
|
3480
|
+
join5(winFonts, "arialbd.ttf"),
|
|
3481
|
+
join5(winFonts, "times.ttf"),
|
|
3482
|
+
join5(winFonts, "cour.ttf"),
|
|
3483
|
+
join5(winFonts, "verdana.ttf"),
|
|
3484
|
+
join5(winFonts, "tahoma.ttf"),
|
|
3485
|
+
join5(winFonts, "georgia.ttf"),
|
|
3486
|
+
join5(winFonts, "consola.ttf"),
|
|
3487
|
+
join5(winFonts, "segoeui.ttf")
|
|
3488
|
+
];
|
|
3489
|
+
}
|
|
3490
|
+
default:
|
|
3491
|
+
return [];
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
function parseSvgDimensions(svg) {
|
|
3495
|
+
const widthMatch = svg.match(/\bwidth\s*=\s*["']([^"']+)["']/);
|
|
3496
|
+
const heightMatch = svg.match(/\bheight\s*=\s*["']([^"']+)["']/);
|
|
3497
|
+
const viewBoxMatch = svg.match(/\bviewBox\s*=\s*["']([^"']+)["']/);
|
|
3498
|
+
let width = widthMatch ? parseFloat(widthMatch[1]) : NaN;
|
|
3499
|
+
let height = heightMatch ? parseFloat(heightMatch[1]) : NaN;
|
|
3500
|
+
if ((isNaN(width) || isNaN(height)) && viewBoxMatch) {
|
|
3501
|
+
const parts = viewBoxMatch[1].split(/[\s,]+/);
|
|
3502
|
+
if (parts.length >= 4) {
|
|
3503
|
+
if (isNaN(width)) width = parseFloat(parts[2]);
|
|
3504
|
+
if (isNaN(height)) height = parseFloat(parts[3]);
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
if (isNaN(width)) width = 300;
|
|
3508
|
+
if (isNaN(height)) height = 150;
|
|
3509
|
+
return { width, height };
|
|
3510
|
+
}
|
|
3511
|
+
function svgUsesExternalFonts(svgData) {
|
|
3512
|
+
const svg = svgData.toString("utf-8");
|
|
3513
|
+
return /<text[\s>]/i.test(svg) || /font-family/i.test(svg) || /@font-face/i.test(svg);
|
|
3514
|
+
}
|
|
3515
|
+
async function rasterizeSvg(svgData) {
|
|
3516
|
+
await ensureInit();
|
|
3517
|
+
const svgString = svgData.toString("utf-8");
|
|
3518
|
+
const { width, height } = parseSvgDimensions(svgString);
|
|
3519
|
+
const maxDim = Math.max(width, height);
|
|
3520
|
+
const scale = Math.min(10, 8e3 / maxDim);
|
|
3521
|
+
const targetWidth = Math.round(width * scale);
|
|
3522
|
+
const resvg = new ResvgClass(svgString, {
|
|
3523
|
+
fitTo: { mode: "width", value: targetWidth },
|
|
3524
|
+
font: {
|
|
3525
|
+
loadSystemFonts: false,
|
|
3526
|
+
fontBuffers,
|
|
3527
|
+
defaultFontFamily: "Helvetica"
|
|
3528
|
+
}
|
|
3529
|
+
});
|
|
3530
|
+
const rendered = resvg.render();
|
|
3531
|
+
const png = Buffer.from(rendered.asPng());
|
|
3532
|
+
rendered.free();
|
|
3533
|
+
resvg.free();
|
|
3534
|
+
return png;
|
|
3535
|
+
}
|
|
3536
|
+
|
|
3400
3537
|
// src/outline/parser.ts
|
|
3401
3538
|
var BRACE_LANGS = /* @__PURE__ */ new Set([
|
|
3402
3539
|
"javascript",
|
|
@@ -3826,6 +3963,23 @@ apiRoutes.patch("/files/:fileId/status", async (c) => {
|
|
|
3826
3963
|
await updateFileStatus(c.req.param("fileId"), status);
|
|
3827
3964
|
return c.json({ ok: true });
|
|
3828
3965
|
});
|
|
3966
|
+
apiRoutes.post("/files/:fileId/reveal", async (c) => {
|
|
3967
|
+
const file = await getReviewFile(c.req.param("fileId"));
|
|
3968
|
+
if (!file) return c.json({ error: "Not found" }, 404);
|
|
3969
|
+
const repoRoot = c.get("repoRoot");
|
|
3970
|
+
const fullPath = resolve3(repoRoot, file.file_path);
|
|
3971
|
+
try {
|
|
3972
|
+
if (process.platform === "darwin") {
|
|
3973
|
+
execSync5(`open -R "${fullPath}"`);
|
|
3974
|
+
} else if (process.platform === "win32") {
|
|
3975
|
+
execSync5(`explorer /select,"${fullPath}"`);
|
|
3976
|
+
} else {
|
|
3977
|
+
execSync5(`xdg-open "${resolve3(fullPath, "..")}"`);
|
|
3978
|
+
}
|
|
3979
|
+
} catch {
|
|
3980
|
+
}
|
|
3981
|
+
return c.json({ ok: true });
|
|
3982
|
+
});
|
|
3829
3983
|
function autoExport(c) {
|
|
3830
3984
|
scheduleAutoExport(c.get("reviewId"), c.get("repoRoot"));
|
|
3831
3985
|
}
|
|
@@ -3916,19 +4070,19 @@ apiRoutes.get("/context/:fileId", async (c) => {
|
|
|
3916
4070
|
return c.json({ lines });
|
|
3917
4071
|
});
|
|
3918
4072
|
function readProjectSettings(repoRoot) {
|
|
3919
|
-
const settingsPath =
|
|
4073
|
+
const settingsPath = join6(repoRoot, ".glassbox", "settings.json");
|
|
3920
4074
|
try {
|
|
3921
|
-
if (
|
|
3922
|
-
return JSON.parse(
|
|
4075
|
+
if (existsSync5(settingsPath)) {
|
|
4076
|
+
return JSON.parse(readFileSync7(settingsPath, "utf-8"));
|
|
3923
4077
|
}
|
|
3924
4078
|
} catch {
|
|
3925
4079
|
}
|
|
3926
4080
|
return {};
|
|
3927
4081
|
}
|
|
3928
4082
|
function writeProjectSettings(repoRoot, settings) {
|
|
3929
|
-
const dir =
|
|
4083
|
+
const dir = join6(repoRoot, ".glassbox");
|
|
3930
4084
|
mkdirSync4(dir, { recursive: true });
|
|
3931
|
-
writeFileSync4(
|
|
4085
|
+
writeFileSync4(join6(dir, "settings.json"), JSON.stringify(settings, null, 2), "utf-8");
|
|
3932
4086
|
}
|
|
3933
4087
|
apiRoutes.get("/project-settings", (c) => {
|
|
3934
4088
|
const repoRoot = c.get("repoRoot");
|
|
@@ -3978,6 +4132,16 @@ apiRoutes.get("/image/:fileId/:side", async (c) => {
|
|
|
3978
4132
|
const oldPath = diff.oldPath ?? null;
|
|
3979
4133
|
const image = side === "old" ? getOldImage(mode, file.file_path, oldPath, repoRoot) : getNewImage(mode, file.file_path, repoRoot);
|
|
3980
4134
|
if (!image) return c.text("Image not available", 404);
|
|
4135
|
+
if (isSvgFile(file.file_path)) {
|
|
4136
|
+
try {
|
|
4137
|
+
const png = await rasterizeSvg(image.data);
|
|
4138
|
+
return new Response(png, {
|
|
4139
|
+
headers: { "Content-Type": "image/png", "Cache-Control": "no-cache" }
|
|
4140
|
+
});
|
|
4141
|
+
} catch {
|
|
4142
|
+
return c.text("SVG rasterization failed", 500);
|
|
4143
|
+
}
|
|
4144
|
+
}
|
|
3981
4145
|
const contentType = getContentType(file.file_path);
|
|
3982
4146
|
return new Response(image.data, {
|
|
3983
4147
|
headers: { "Content-Type": contentType, "Cache-Control": "no-cache" }
|
|
@@ -4057,7 +4221,7 @@ function jsx(tag, props) {
|
|
|
4057
4221
|
}
|
|
4058
4222
|
|
|
4059
4223
|
// src/components/imageDiff.tsx
|
|
4060
|
-
function ImageDiff({ file, diff }) {
|
|
4224
|
+
function ImageDiff({ file, diff, fontWarning, baseWidth, baseHeight }) {
|
|
4061
4225
|
const fileId = file.id;
|
|
4062
4226
|
const isAdded = diff.status === "added";
|
|
4063
4227
|
const isDeleted = diff.status === "deleted";
|
|
@@ -4072,7 +4236,10 @@ function ImageDiff({ file, diff }) {
|
|
|
4072
4236
|
"data-file-path": file.file_path,
|
|
4073
4237
|
"data-has-old": String(hasOld),
|
|
4074
4238
|
"data-has-new": String(hasNew),
|
|
4239
|
+
...baseWidth ? { "data-base-width": String(baseWidth) } : {},
|
|
4240
|
+
...baseHeight ? { "data-base-height": String(baseHeight) } : {},
|
|
4075
4241
|
children: [
|
|
4242
|
+
fontWarning && /* @__PURE__ */ jsx("div", { className: "image-font-warning", children: "This SVG uses text that may render differently depending on locally installed fonts." }),
|
|
4076
4243
|
/* @__PURE__ */ jsx("div", { className: "image-diff-panel image-diff-metadata active", "data-panel": "metadata", children: /* @__PURE__ */ jsx("div", { className: "image-metadata-loading", children: "Loading metadata..." }) }),
|
|
4077
4244
|
hasComparison && /* @__PURE__ */ jsx("div", { className: "image-diff-panel image-diff-visual", "data-panel": "difference", children: /* @__PURE__ */ jsx("div", { className: "image-visual-canvas", "data-zoomable": "true", children: /* @__PURE__ */ jsx("div", { className: "image-zoom-wrap", children: [
|
|
4078
4245
|
/* @__PURE__ */ jsx("img", { className: "image-layer image-layer-old", src: `/api/image/${fileId}/old`, alt: "Old version" }),
|
|
@@ -4101,6 +4268,7 @@ function ImageDiff({ file, diff }) {
|
|
|
4101
4268
|
}
|
|
4102
4269
|
|
|
4103
4270
|
// src/components/diffView.tsx
|
|
4271
|
+
var revealSvg = '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/><line x1="12" y1="11" x2="12" y2="17"/><polyline points="9 14 12 11 15 14"/></svg>';
|
|
4104
4272
|
function DiffView({ file, diff, annotations, mode }) {
|
|
4105
4273
|
const annotationsByLine = {};
|
|
4106
4274
|
for (const a of annotations) {
|
|
@@ -4108,13 +4276,25 @@ function DiffView({ file, diff, annotations, mode }) {
|
|
|
4108
4276
|
if (!(key in annotationsByLine)) annotationsByLine[key] = [];
|
|
4109
4277
|
annotationsByLine[key].push(a);
|
|
4110
4278
|
}
|
|
4111
|
-
return /* @__PURE__ */ jsx(
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4279
|
+
return /* @__PURE__ */ jsx(
|
|
4280
|
+
"div",
|
|
4281
|
+
{
|
|
4282
|
+
className: "diff-view",
|
|
4283
|
+
"data-file-id": file.id,
|
|
4284
|
+
"data-file-path": file.file_path,
|
|
4285
|
+
...isSvgFile(diff.filePath) ? { "data-is-svg": "true" } : {},
|
|
4286
|
+
children: [
|
|
4287
|
+
/* @__PURE__ */ jsx("div", { className: "diff-header", children: [
|
|
4288
|
+
/* @__PURE__ */ jsx("div", { className: "diff-header-file", children: [
|
|
4289
|
+
/* @__PURE__ */ jsx("span", { className: "file-path", children: diff.filePath }),
|
|
4290
|
+
/* @__PURE__ */ jsx("button", { className: "reveal-btn", "data-file-id": file.id, title: "Reveal in file manager", children: raw(revealSvg) })
|
|
4291
|
+
] }),
|
|
4292
|
+
/* @__PURE__ */ jsx("div", { className: "diff-header-actions", children: /* @__PURE__ */ jsx("span", { className: `file-status ${diff.status}`, children: diff.status }) })
|
|
4293
|
+
] }),
|
|
4294
|
+
diff.isBinary && isImageFile(diff.filePath) ? /* @__PURE__ */ jsx(ImageDiff, { file, diff }) : diff.isBinary ? /* @__PURE__ */ jsx("div", { className: "hunk-separator", children: "Binary file" }) : diff.status === "added" || diff.status === "deleted" || mode === "unified" ? /* @__PURE__ */ jsx(UnifiedDiff, { hunks: diff.hunks, annotationsByLine }) : /* @__PURE__ */ jsx(SplitDiff, { hunks: diff.hunks, annotationsByLine })
|
|
4295
|
+
]
|
|
4296
|
+
}
|
|
4297
|
+
);
|
|
4118
4298
|
}
|
|
4119
4299
|
function getAnnotations(pair, annotationsByLine) {
|
|
4120
4300
|
const leftAnns = pair.left ? annotationsByLine[`${pair.left.oldNum}:old`] ?? [] : [];
|
|
@@ -4637,6 +4817,7 @@ var zoomOutSvg = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14"
|
|
|
4637
4817
|
var zoomInSvg = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>';
|
|
4638
4818
|
var actualSizeSvg = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><text x="12" y="15.5" text-anchor="middle" font-size="9" font-weight="bold" fill="currentColor" stroke="none">1:1</text></svg>';
|
|
4639
4819
|
var fitSvg = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6"/><path d="M9 21H3v-6"/><path d="M21 3l-7 7"/><path d="M3 21l7-7"/></svg>';
|
|
4820
|
+
var revealSvgIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/><line x1="12" y1="11" x2="12" y2="17"/><polyline points="9 14 12 11 15 14"/></svg>';
|
|
4640
4821
|
var pageRoutes = new Hono3();
|
|
4641
4822
|
pageRoutes.get("/", async (c) => {
|
|
4642
4823
|
const reviewId = c.get("reviewId");
|
|
@@ -4684,6 +4865,10 @@ pageRoutes.get("/", async (c) => {
|
|
|
4684
4865
|
] }),
|
|
4685
4866
|
/* @__PURE__ */ jsx("div", { className: "diff-container", id: "diff-container", style: "display:none" }),
|
|
4686
4867
|
/* @__PURE__ */ jsx("div", { className: "diff-toolbar", id: "diff-toolbar", style: "display:none", children: [
|
|
4868
|
+
/* @__PURE__ */ jsx("div", { className: "diff-toolbar-svg-toggle", style: "display:none", children: /* @__PURE__ */ jsx("div", { className: "segmented-control", children: [
|
|
4869
|
+
/* @__PURE__ */ jsx("button", { className: "segment active", "data-svg-mode": "code", children: "Code" }),
|
|
4870
|
+
/* @__PURE__ */ jsx("button", { className: "segment", "data-svg-mode": "rendered", children: "Rendered" })
|
|
4871
|
+
] }) }),
|
|
4687
4872
|
/* @__PURE__ */ jsx("div", { className: "diff-toolbar-text", children: [
|
|
4688
4873
|
/* @__PURE__ */ jsx("div", { className: "diff-toolbar-left", children: [
|
|
4689
4874
|
/* @__PURE__ */ jsx("div", { className: "segmented-control", children: [
|
|
@@ -4719,10 +4904,53 @@ pageRoutes.get("/file/:fileId", async (c) => {
|
|
|
4719
4904
|
const fileId = c.req.param("fileId");
|
|
4720
4905
|
const mode = c.req.query("mode") === "unified" ? "unified" : "split";
|
|
4721
4906
|
const ignoreWhitespace = c.req.query("ignoreWhitespace") === "1";
|
|
4907
|
+
const view = c.req.query("view");
|
|
4722
4908
|
const file = await getReviewFile(fileId);
|
|
4723
4909
|
if (!file) return c.text("File not found", 404);
|
|
4910
|
+
const diff = JSON.parse(file.diff_data ?? "{}");
|
|
4911
|
+
if (view === "rendered" && isSvgFile(file.file_path)) {
|
|
4912
|
+
const repoRoot = c.get("repoRoot");
|
|
4913
|
+
const review = await getReview(file.review_id);
|
|
4914
|
+
let fontWarning = false;
|
|
4915
|
+
let svgBaseWidth = 300;
|
|
4916
|
+
let svgBaseHeight = 150;
|
|
4917
|
+
if (review) {
|
|
4918
|
+
const reviewMode = parseModeString(review.mode);
|
|
4919
|
+
const oldImg = diff.status !== "added" ? getOldImage(reviewMode, file.file_path, diff.oldPath ?? null, repoRoot) : null;
|
|
4920
|
+
const newImg = diff.status !== "deleted" ? getNewImage(reviewMode, file.file_path, repoRoot) : null;
|
|
4921
|
+
const svgData = newImg ?? oldImg;
|
|
4922
|
+
if (svgData) {
|
|
4923
|
+
const dims = parseSvgDimensions(svgData.data.toString("utf-8"));
|
|
4924
|
+
svgBaseWidth = dims.width;
|
|
4925
|
+
svgBaseHeight = dims.height;
|
|
4926
|
+
}
|
|
4927
|
+
if (oldImg && svgUsesExternalFonts(oldImg.data) || newImg && svgUsesExternalFonts(newImg.data)) {
|
|
4928
|
+
fontWarning = true;
|
|
4929
|
+
}
|
|
4930
|
+
}
|
|
4931
|
+
const html2 = /* @__PURE__ */ jsx("div", { className: "diff-view", "data-file-id": file.id, "data-file-path": file.file_path, "data-is-svg": "true", children: [
|
|
4932
|
+
/* @__PURE__ */ jsx("div", { className: "diff-header", children: [
|
|
4933
|
+
/* @__PURE__ */ jsx("div", { className: "diff-header-file", children: [
|
|
4934
|
+
/* @__PURE__ */ jsx("span", { className: "file-path", children: diff.filePath }),
|
|
4935
|
+
/* @__PURE__ */ jsx("button", { className: "reveal-btn", "data-file-id": file.id, title: "Reveal in file manager", children: raw(revealSvgIcon) })
|
|
4936
|
+
] }),
|
|
4937
|
+
/* @__PURE__ */ jsx("div", { className: "diff-header-actions", children: /* @__PURE__ */ jsx("span", { className: `file-status ${diff.status}`, children: diff.status }) })
|
|
4938
|
+
] }),
|
|
4939
|
+
/* @__PURE__ */ jsx(
|
|
4940
|
+
ImageDiff,
|
|
4941
|
+
{
|
|
4942
|
+
file,
|
|
4943
|
+
diff,
|
|
4944
|
+
fontWarning,
|
|
4945
|
+
baseWidth: svgBaseWidth,
|
|
4946
|
+
baseHeight: svgBaseHeight
|
|
4947
|
+
}
|
|
4948
|
+
)
|
|
4949
|
+
] });
|
|
4950
|
+
return c.html(html2.toString());
|
|
4951
|
+
}
|
|
4724
4952
|
const annotations = await getAnnotationsForFile(fileId);
|
|
4725
|
-
let
|
|
4953
|
+
let finalDiff = diff;
|
|
4726
4954
|
if (ignoreWhitespace) {
|
|
4727
4955
|
const repoRoot = c.get("repoRoot");
|
|
4728
4956
|
const review = await getReview(file.review_id);
|
|
@@ -4730,11 +4958,11 @@ pageRoutes.get("/file/:fileId", async (c) => {
|
|
|
4730
4958
|
const reviewMode = parseModeString(review.mode);
|
|
4731
4959
|
const regenerated = getSingleFileDiff(reviewMode, file.file_path, repoRoot, "-w");
|
|
4732
4960
|
if (regenerated) {
|
|
4733
|
-
|
|
4961
|
+
finalDiff = regenerated;
|
|
4734
4962
|
}
|
|
4735
4963
|
}
|
|
4736
4964
|
}
|
|
4737
|
-
const html = /* @__PURE__ */ jsx(DiffView, { file, diff, annotations, mode });
|
|
4965
|
+
const html = /* @__PURE__ */ jsx(DiffView, { file, diff: finalDiff, annotations, mode });
|
|
4738
4966
|
return c.html(html.toString());
|
|
4739
4967
|
});
|
|
4740
4968
|
pageRoutes.get("/review/:reviewId", async (c) => {
|
|
@@ -4788,6 +5016,10 @@ pageRoutes.get("/review/:reviewId", async (c) => {
|
|
|
4788
5016
|
] }),
|
|
4789
5017
|
/* @__PURE__ */ jsx("div", { className: "diff-container", id: "diff-container", style: "display:none" }),
|
|
4790
5018
|
/* @__PURE__ */ jsx("div", { className: "diff-toolbar", id: "diff-toolbar", style: "display:none", children: [
|
|
5019
|
+
/* @__PURE__ */ jsx("div", { className: "diff-toolbar-svg-toggle", style: "display:none", children: /* @__PURE__ */ jsx("div", { className: "segmented-control", children: [
|
|
5020
|
+
/* @__PURE__ */ jsx("button", { className: "segment active", "data-svg-mode": "code", children: "Code" }),
|
|
5021
|
+
/* @__PURE__ */ jsx("button", { className: "segment", "data-svg-mode": "rendered", children: "Rendered" })
|
|
5022
|
+
] }) }),
|
|
4791
5023
|
/* @__PURE__ */ jsx("div", { className: "diff-toolbar-text", children: [
|
|
4792
5024
|
/* @__PURE__ */ jsx("div", { className: "diff-toolbar-left", children: [
|
|
4793
5025
|
/* @__PURE__ */ jsx("div", { className: "segmented-control", children: [
|
|
@@ -4829,10 +5061,10 @@ pageRoutes.get("/history", async (c) => {
|
|
|
4829
5061
|
|
|
4830
5062
|
// src/server.ts
|
|
4831
5063
|
function tryServe(fetch2, port) {
|
|
4832
|
-
return new Promise((
|
|
5064
|
+
return new Promise((resolve5, reject) => {
|
|
4833
5065
|
const server = serve({ fetch: fetch2, port });
|
|
4834
5066
|
server.on("listening", () => {
|
|
4835
|
-
|
|
5067
|
+
resolve5(port);
|
|
4836
5068
|
});
|
|
4837
5069
|
server.on("error", (err) => {
|
|
4838
5070
|
if (err.code === "EADDRINUSE") {
|
|
@@ -4852,13 +5084,13 @@ async function startServer(port, reviewId, repoRoot, options) {
|
|
|
4852
5084
|
await next();
|
|
4853
5085
|
});
|
|
4854
5086
|
const selfDir = dirname(fileURLToPath(import.meta.url));
|
|
4855
|
-
const distDir =
|
|
5087
|
+
const distDir = existsSync6(join7(selfDir, "client", "styles.css")) ? join7(selfDir, "client") : join7(selfDir, "..", "dist", "client");
|
|
4856
5088
|
app.get("/static/styles.css", (c) => {
|
|
4857
|
-
const css =
|
|
5089
|
+
const css = readFileSync8(join7(distDir, "styles.css"), "utf-8");
|
|
4858
5090
|
return c.text(css, 200, { "Content-Type": "text/css", "Cache-Control": "no-cache" });
|
|
4859
5091
|
});
|
|
4860
5092
|
app.get("/static/app.js", (c) => {
|
|
4861
|
-
const js =
|
|
5093
|
+
const js = readFileSync8(join7(distDir, "app.global.js"), "utf-8");
|
|
4862
5094
|
return c.text(js, 200, { "Content-Type": "application/javascript", "Cache-Control": "no-cache" });
|
|
4863
5095
|
});
|
|
4864
5096
|
app.route("/api", apiRoutes);
|
|
@@ -4894,8 +5126,8 @@ async function startServer(port, reviewId, repoRoot, options) {
|
|
|
4894
5126
|
}
|
|
4895
5127
|
|
|
4896
5128
|
// src/skills.ts
|
|
4897
|
-
import { existsSync as
|
|
4898
|
-
import { join as
|
|
5129
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
|
|
5130
|
+
import { join as join8 } from "path";
|
|
4899
5131
|
var SKILL_VERSION = 1;
|
|
4900
5132
|
function versionHeader() {
|
|
4901
5133
|
return `<!-- glassbox-skill-version: ${SKILL_VERSION} -->`;
|
|
@@ -4906,8 +5138,8 @@ function parseVersionHeader(content) {
|
|
|
4906
5138
|
return parseInt(match[1], 10);
|
|
4907
5139
|
}
|
|
4908
5140
|
function updateFile(path, content) {
|
|
4909
|
-
if (
|
|
4910
|
-
const existing =
|
|
5141
|
+
if (existsSync7(path)) {
|
|
5142
|
+
const existing = readFileSync9(path, "utf-8");
|
|
4911
5143
|
const version = parseVersionHeader(existing);
|
|
4912
5144
|
if (version !== null && version >= SKILL_VERSION) {
|
|
4913
5145
|
return false;
|
|
@@ -4933,7 +5165,7 @@ function skillBody() {
|
|
|
4933
5165
|
].join("\n");
|
|
4934
5166
|
}
|
|
4935
5167
|
function ensureClaudeSkills(cwd) {
|
|
4936
|
-
const dir =
|
|
5168
|
+
const dir = join8(cwd, ".claude", "skills", "glassbox");
|
|
4937
5169
|
mkdirSync5(dir, { recursive: true });
|
|
4938
5170
|
const content = [
|
|
4939
5171
|
"---",
|
|
@@ -4946,10 +5178,10 @@ function ensureClaudeSkills(cwd) {
|
|
|
4946
5178
|
skillBody(),
|
|
4947
5179
|
""
|
|
4948
5180
|
].join("\n");
|
|
4949
|
-
return updateFile(
|
|
5181
|
+
return updateFile(join8(dir, "SKILL.md"), content);
|
|
4950
5182
|
}
|
|
4951
5183
|
function ensureCursorRules(cwd) {
|
|
4952
|
-
const rulesDir =
|
|
5184
|
+
const rulesDir = join8(cwd, ".cursor", "rules");
|
|
4953
5185
|
mkdirSync5(rulesDir, { recursive: true });
|
|
4954
5186
|
const content = [
|
|
4955
5187
|
"---",
|
|
@@ -4961,10 +5193,10 @@ function ensureCursorRules(cwd) {
|
|
|
4961
5193
|
skillBody(),
|
|
4962
5194
|
""
|
|
4963
5195
|
].join("\n");
|
|
4964
|
-
return updateFile(
|
|
5196
|
+
return updateFile(join8(rulesDir, "glassbox.mdc"), content);
|
|
4965
5197
|
}
|
|
4966
5198
|
function ensureCopilotPrompts(cwd) {
|
|
4967
|
-
const promptsDir =
|
|
5199
|
+
const promptsDir = join8(cwd, ".github", "prompts");
|
|
4968
5200
|
mkdirSync5(promptsDir, { recursive: true });
|
|
4969
5201
|
const content = [
|
|
4970
5202
|
"---",
|
|
@@ -4975,10 +5207,10 @@ function ensureCopilotPrompts(cwd) {
|
|
|
4975
5207
|
skillBody(),
|
|
4976
5208
|
""
|
|
4977
5209
|
].join("\n");
|
|
4978
|
-
return updateFile(
|
|
5210
|
+
return updateFile(join8(promptsDir, "glassbox.prompt.md"), content);
|
|
4979
5211
|
}
|
|
4980
5212
|
function ensureWindsurfRules(cwd) {
|
|
4981
|
-
const rulesDir =
|
|
5213
|
+
const rulesDir = join8(cwd, ".windsurf", "rules");
|
|
4982
5214
|
mkdirSync5(rulesDir, { recursive: true });
|
|
4983
5215
|
const content = [
|
|
4984
5216
|
"---",
|
|
@@ -4990,39 +5222,39 @@ function ensureWindsurfRules(cwd) {
|
|
|
4990
5222
|
skillBody(),
|
|
4991
5223
|
""
|
|
4992
5224
|
].join("\n");
|
|
4993
|
-
return updateFile(
|
|
5225
|
+
return updateFile(join8(rulesDir, "glassbox.md"), content);
|
|
4994
5226
|
}
|
|
4995
5227
|
function ensureSkills() {
|
|
4996
5228
|
const cwd = process.cwd();
|
|
4997
5229
|
const platforms = [];
|
|
4998
|
-
if (
|
|
5230
|
+
if (existsSync7(join8(cwd, ".claude"))) {
|
|
4999
5231
|
if (ensureClaudeSkills(cwd)) platforms.push("Claude Code");
|
|
5000
5232
|
}
|
|
5001
|
-
if (
|
|
5233
|
+
if (existsSync7(join8(cwd, ".cursor"))) {
|
|
5002
5234
|
if (ensureCursorRules(cwd)) platforms.push("Cursor");
|
|
5003
5235
|
}
|
|
5004
|
-
if (
|
|
5236
|
+
if (existsSync7(join8(cwd, ".github", "prompts")) || existsSync7(join8(cwd, ".github", "copilot-instructions.md"))) {
|
|
5005
5237
|
if (ensureCopilotPrompts(cwd)) platforms.push("GitHub Copilot");
|
|
5006
5238
|
}
|
|
5007
|
-
if (
|
|
5239
|
+
if (existsSync7(join8(cwd, ".windsurf"))) {
|
|
5008
5240
|
if (ensureWindsurfRules(cwd)) platforms.push("Windsurf");
|
|
5009
5241
|
}
|
|
5010
5242
|
return platforms;
|
|
5011
5243
|
}
|
|
5012
5244
|
|
|
5013
5245
|
// src/update-check.ts
|
|
5014
|
-
import { existsSync as
|
|
5246
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
|
|
5015
5247
|
import { get } from "https";
|
|
5016
5248
|
import { homedir as homedir3 } from "os";
|
|
5017
|
-
import { dirname as dirname2, join as
|
|
5249
|
+
import { dirname as dirname2, join as join9 } from "path";
|
|
5018
5250
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5019
|
-
var DATA_DIR =
|
|
5020
|
-
var CHECK_FILE =
|
|
5251
|
+
var DATA_DIR = join9(homedir3(), ".glassbox");
|
|
5252
|
+
var CHECK_FILE = join9(DATA_DIR, "last-update-check");
|
|
5021
5253
|
var PACKAGE_NAME = "glassbox";
|
|
5022
5254
|
function getCurrentVersion() {
|
|
5023
5255
|
try {
|
|
5024
5256
|
const dir = dirname2(fileURLToPath2(import.meta.url));
|
|
5025
|
-
const pkg = JSON.parse(
|
|
5257
|
+
const pkg = JSON.parse(readFileSync10(join9(dir, "..", "package.json"), "utf-8"));
|
|
5026
5258
|
return pkg.version;
|
|
5027
5259
|
} catch {
|
|
5028
5260
|
return "0.0.0";
|
|
@@ -5030,8 +5262,8 @@ function getCurrentVersion() {
|
|
|
5030
5262
|
}
|
|
5031
5263
|
function getLastCheckDate() {
|
|
5032
5264
|
try {
|
|
5033
|
-
if (
|
|
5034
|
-
return
|
|
5265
|
+
if (existsSync8(CHECK_FILE)) {
|
|
5266
|
+
return readFileSync10(CHECK_FILE, "utf-8").trim();
|
|
5035
5267
|
}
|
|
5036
5268
|
} catch {
|
|
5037
5269
|
}
|
|
@@ -5048,10 +5280,10 @@ function isFirstUseToday() {
|
|
|
5048
5280
|
return last !== today;
|
|
5049
5281
|
}
|
|
5050
5282
|
function fetchLatestVersion() {
|
|
5051
|
-
return new Promise((
|
|
5283
|
+
return new Promise((resolve5) => {
|
|
5052
5284
|
const req = get(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, { timeout: 5e3 }, (res) => {
|
|
5053
5285
|
if (res.statusCode !== 200) {
|
|
5054
|
-
|
|
5286
|
+
resolve5(null);
|
|
5055
5287
|
return;
|
|
5056
5288
|
}
|
|
5057
5289
|
let data = "";
|
|
@@ -5060,18 +5292,18 @@ function fetchLatestVersion() {
|
|
|
5060
5292
|
});
|
|
5061
5293
|
res.on("end", () => {
|
|
5062
5294
|
try {
|
|
5063
|
-
|
|
5295
|
+
resolve5(JSON.parse(data).version);
|
|
5064
5296
|
} catch {
|
|
5065
|
-
|
|
5297
|
+
resolve5(null);
|
|
5066
5298
|
}
|
|
5067
5299
|
});
|
|
5068
5300
|
});
|
|
5069
5301
|
req.on("error", () => {
|
|
5070
|
-
|
|
5302
|
+
resolve5(null);
|
|
5071
5303
|
});
|
|
5072
5304
|
req.on("timeout", () => {
|
|
5073
5305
|
req.destroy();
|
|
5074
|
-
|
|
5306
|
+
resolve5(null);
|
|
5075
5307
|
});
|
|
5076
5308
|
});
|
|
5077
5309
|
}
|
|
@@ -5209,7 +5441,7 @@ function parseArgs(argv) {
|
|
|
5209
5441
|
port = parseInt(args[++i], 10);
|
|
5210
5442
|
break;
|
|
5211
5443
|
case "--data-dir":
|
|
5212
|
-
dataDir =
|
|
5444
|
+
dataDir = resolve4(args[++i]);
|
|
5213
5445
|
break;
|
|
5214
5446
|
case "--resume":
|
|
5215
5447
|
resume = true;
|
|
@@ -5265,13 +5497,13 @@ async function main() {
|
|
|
5265
5497
|
console.log("AI service test mode enabled \u2014 using mock AI responses");
|
|
5266
5498
|
}
|
|
5267
5499
|
if (debug) {
|
|
5268
|
-
console.log(`[debug] Build timestamp: ${"2026-03-
|
|
5500
|
+
console.log(`[debug] Build timestamp: ${"2026-03-19T03:31:50.984Z"}`);
|
|
5269
5501
|
}
|
|
5270
5502
|
if (projectDir) {
|
|
5271
5503
|
process.chdir(projectDir);
|
|
5272
5504
|
}
|
|
5273
5505
|
if (dataDir === null) {
|
|
5274
|
-
dataDir =
|
|
5506
|
+
dataDir = join10(process.cwd(), ".glassbox");
|
|
5275
5507
|
}
|
|
5276
5508
|
if (demo !== null) {
|
|
5277
5509
|
const scenario = DEMO_SCENARIOS.find((s) => s.id === demo);
|
|
@@ -5283,7 +5515,7 @@ async function main() {
|
|
|
5283
5515
|
}
|
|
5284
5516
|
process.exit(1);
|
|
5285
5517
|
}
|
|
5286
|
-
dataDir =
|
|
5518
|
+
dataDir = join10(tmpdir(), `glassbox-demo-${demo}-${Date.now()}`);
|
|
5287
5519
|
setDemoMode(demo);
|
|
5288
5520
|
console.log(`
|
|
5289
5521
|
DEMO MODE: ${scenario.label}
|