glassbox 0.16.0 → 0.17.0-beta.3
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 +398 -485
- package/dist/client/app.global.js +33 -33
- package/dist/client/styles.css +1 -1
- package/package.json +2 -2
- package/dist/svg-rasterize-worker.js +0 -154
package/dist/cli.js
CHANGED
|
@@ -46,6 +46,8 @@ var init_schema = __esm({
|
|
|
46
46
|
CREATE TABLE IF NOT EXISTS annotations (
|
|
47
47
|
id TEXT PRIMARY KEY,
|
|
48
48
|
review_file_id TEXT NOT NULL REFERENCES review_files(id) ON DELETE CASCADE,
|
|
49
|
+
-- 0 marks an image-level annotation (doc 23); line annotations use the
|
|
50
|
+
-- real 1-based line number.
|
|
49
51
|
line_number INTEGER NOT NULL,
|
|
50
52
|
side TEXT NOT NULL DEFAULT 'new',
|
|
51
53
|
category TEXT NOT NULL DEFAULT 'note',
|
|
@@ -53,6 +55,9 @@ var init_schema = __esm({
|
|
|
53
55
|
is_stale BOOLEAN NOT NULL DEFAULT FALSE,
|
|
54
56
|
original_content TEXT,
|
|
55
57
|
reply_to_note_id TEXT,
|
|
58
|
+
-- JSON {x,y,w,h} normalized fractions for an image-region annotation
|
|
59
|
+
-- (doc 23), or NULL for line annotations and general image comments.
|
|
60
|
+
region_data TEXT,
|
|
56
61
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
57
62
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
|
58
63
|
);
|
|
@@ -148,6 +153,7 @@ async function initSchema(db2) {
|
|
|
148
153
|
await addColumnIfMissing(db2, "annotations", "is_stale", "BOOLEAN NOT NULL DEFAULT FALSE");
|
|
149
154
|
await addColumnIfMissing(db2, "annotations", "original_content", "TEXT");
|
|
150
155
|
await addColumnIfMissing(db2, "annotations", "reply_to_note_id", "TEXT");
|
|
156
|
+
await addColumnIfMissing(db2, "annotations", "region_data", "TEXT");
|
|
151
157
|
await addColumnIfMissing(db2, "ai_file_scores", "notes", "TEXT");
|
|
152
158
|
await addColumnIfMissing(db2, "ai_analyses", "progress_completed", "INTEGER NOT NULL DEFAULT 0");
|
|
153
159
|
await addColumnIfMissing(db2, "ai_analyses", "progress_total", "INTEGER NOT NULL DEFAULT 0");
|
|
@@ -15382,7 +15388,7 @@ function parseJsonColumn(schema, raw2) {
|
|
|
15382
15388
|
const result = schema.safeParse(parsed);
|
|
15383
15389
|
return result.success ? result.data : null;
|
|
15384
15390
|
}
|
|
15385
|
-
var TimestampSchema, ReviewSchema, ReviewFileSchema, AnnotationSchema, AnnotationWithFilePathSchema, AIAnalysisSchema, AIFileScoreSchema, UserPreferencesSchema, DimensionScoresSchema, FileScoreNotesSchema;
|
|
15391
|
+
var TimestampSchema, ReviewSchema, ReviewFileSchema, ImageRegionSchema, AnnotationSchema, AnnotationWithFilePathSchema, AIAnalysisSchema, AIFileScoreSchema, UserPreferencesSchema, DimensionScoresSchema, FileScoreNotesSchema;
|
|
15386
15392
|
var init_schemas3 = __esm({
|
|
15387
15393
|
"src/db/schemas.ts"() {
|
|
15388
15394
|
"use strict";
|
|
@@ -15414,10 +15420,24 @@ var init_schemas3 = __esm({
|
|
|
15414
15420
|
diff_data: external_exports.string().nullable(),
|
|
15415
15421
|
created_at: TimestampSchema
|
|
15416
15422
|
});
|
|
15423
|
+
ImageRegionSchema = external_exports.object({
|
|
15424
|
+
x: external_exports.number().min(0).max(1),
|
|
15425
|
+
y: external_exports.number().min(0).max(1),
|
|
15426
|
+
w: external_exports.number().min(0).max(1),
|
|
15427
|
+
h: external_exports.number().min(0).max(1),
|
|
15428
|
+
// Optional per-side scope (doc 23 §23.6 / §23.10): `old` = applies to the A
|
|
15429
|
+
// (old) image only, `new` = applies to the B (new) image only. Absent means
|
|
15430
|
+
// the region applies to both sides (the default), which is how every region
|
|
15431
|
+
// created before this field existed is interpreted.
|
|
15432
|
+
side: external_exports.enum(["old", "new"]).optional()
|
|
15433
|
+
});
|
|
15417
15434
|
AnnotationSchema = external_exports.object({
|
|
15418
15435
|
id: external_exports.string(),
|
|
15419
|
-
|
|
15436
|
+
// 0 marks an image-level annotation (doc 23): a general image comment, or —
|
|
15437
|
+
// when `region_data` is set — a comment anchored to a rectangle on the image.
|
|
15438
|
+
// Line-anchored text-diff annotations use the real 1-based line number.
|
|
15420
15439
|
line_number: external_exports.number(),
|
|
15440
|
+
review_file_id: external_exports.string(),
|
|
15421
15441
|
side: external_exports.string(),
|
|
15422
15442
|
category: external_exports.string(),
|
|
15423
15443
|
content: external_exports.string(),
|
|
@@ -15427,6 +15447,10 @@ var init_schemas3 = __esm({
|
|
|
15427
15447
|
// threading), or null for a normal annotation. `.default(null)` tolerates
|
|
15428
15448
|
// rows written before the column existed.
|
|
15429
15449
|
reply_to_note_id: external_exports.string().nullable().default(null),
|
|
15450
|
+
// JSON-encoded {@link ImageRegion} for image-region annotations (doc 23), or
|
|
15451
|
+
// null for line annotations and general image comments. `.default(null)`
|
|
15452
|
+
// tolerates rows written before the column existed.
|
|
15453
|
+
region_data: external_exports.string().nullable().default(null),
|
|
15430
15454
|
created_at: TimestampSchema,
|
|
15431
15455
|
updated_at: TimestampSchema
|
|
15432
15456
|
});
|
|
@@ -15501,6 +15525,7 @@ __export(queries_exports, {
|
|
|
15501
15525
|
markAnnotationStale: () => markAnnotationStale,
|
|
15502
15526
|
moveAnnotation: () => moveAnnotation,
|
|
15503
15527
|
updateAnnotation: () => updateAnnotation,
|
|
15528
|
+
updateAnnotationRegion: () => updateAnnotationRegion,
|
|
15504
15529
|
updateFileDiff: () => updateFileDiff,
|
|
15505
15530
|
updateFileStatus: () => updateFileStatus,
|
|
15506
15531
|
updateReviewHead: () => updateReviewHead,
|
|
@@ -15598,13 +15623,22 @@ async function deleteReviewFile(id) {
|
|
|
15598
15623
|
await db2.query("DELETE FROM annotations WHERE review_file_id = $1", [id]);
|
|
15599
15624
|
await db2.query("DELETE FROM review_files WHERE id = $1", [id]);
|
|
15600
15625
|
}
|
|
15601
|
-
async function addAnnotation(reviewFileId, lineNumber, side, category, content, replyToNoteId) {
|
|
15626
|
+
async function addAnnotation(reviewFileId, lineNumber, side, category, content, replyToNoteId, region) {
|
|
15602
15627
|
const db2 = await getDb();
|
|
15603
15628
|
const id = generateId();
|
|
15604
15629
|
const result = await db2.query(
|
|
15605
|
-
`INSERT INTO annotations (id, review_file_id, line_number, side, category, content, reply_to_note_id)
|
|
15606
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *`,
|
|
15607
|
-
[
|
|
15630
|
+
`INSERT INTO annotations (id, review_file_id, line_number, side, category, content, reply_to_note_id, region_data)
|
|
15631
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *`,
|
|
15632
|
+
[
|
|
15633
|
+
id,
|
|
15634
|
+
reviewFileId,
|
|
15635
|
+
lineNumber,
|
|
15636
|
+
side,
|
|
15637
|
+
category,
|
|
15638
|
+
content,
|
|
15639
|
+
replyToNoteId ?? null,
|
|
15640
|
+
region !== void 0 ? JSON.stringify(region) : null
|
|
15641
|
+
]
|
|
15608
15642
|
);
|
|
15609
15643
|
const annotation = parseRow(AnnotationSchema, result.rows[0]);
|
|
15610
15644
|
if (annotation === void 0) throw new Error("addAnnotation: INSERT did not return a row");
|
|
@@ -15640,6 +15674,13 @@ async function deleteAnnotation(id) {
|
|
|
15640
15674
|
const db2 = await getDb();
|
|
15641
15675
|
await db2.query("DELETE FROM annotations WHERE id = $1", [id]);
|
|
15642
15676
|
}
|
|
15677
|
+
async function updateAnnotationRegion(id, region) {
|
|
15678
|
+
const db2 = await getDb();
|
|
15679
|
+
await db2.query(
|
|
15680
|
+
"UPDATE annotations SET region_data = $1, updated_at = NOW() WHERE id = $2",
|
|
15681
|
+
[JSON.stringify(region), id]
|
|
15682
|
+
);
|
|
15683
|
+
}
|
|
15643
15684
|
async function moveAnnotation(id, lineNumber, side) {
|
|
15644
15685
|
const db2 = await getDb();
|
|
15645
15686
|
await db2.query(
|
|
@@ -15725,6 +15766,48 @@ var init_queries = __esm({
|
|
|
15725
15766
|
}
|
|
15726
15767
|
});
|
|
15727
15768
|
|
|
15769
|
+
// src/git/image-blobs.ts
|
|
15770
|
+
var image_blobs_exports = {};
|
|
15771
|
+
__export(image_blobs_exports, {
|
|
15772
|
+
clearImageBlobs: () => clearImageBlobs,
|
|
15773
|
+
readImageBlob: () => readImageBlob,
|
|
15774
|
+
writeImageBlob: () => writeImageBlob
|
|
15775
|
+
});
|
|
15776
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
15777
|
+
import { join as join3 } from "path";
|
|
15778
|
+
function blobDir(dataDir) {
|
|
15779
|
+
return join3(dataDir, "image-blobs");
|
|
15780
|
+
}
|
|
15781
|
+
function blobName(fileId, side) {
|
|
15782
|
+
return `${fileId.replace(/[^a-z0-9]/gi, "")}-${side}`;
|
|
15783
|
+
}
|
|
15784
|
+
function writeImageBlob(dataDir, fileId, side, bytes) {
|
|
15785
|
+
if (bytes.length === 0) return;
|
|
15786
|
+
const dir = blobDir(dataDir);
|
|
15787
|
+
mkdirSync3(dir, { recursive: true });
|
|
15788
|
+
writeFileSync2(join3(dir, blobName(fileId, side)), bytes);
|
|
15789
|
+
}
|
|
15790
|
+
function readImageBlob(dataDir, fileId, side) {
|
|
15791
|
+
const path = join3(blobDir(dataDir), blobName(fileId, side));
|
|
15792
|
+
if (!existsSync2(path)) return null;
|
|
15793
|
+
try {
|
|
15794
|
+
return readFileSync2(path);
|
|
15795
|
+
} catch {
|
|
15796
|
+
return null;
|
|
15797
|
+
}
|
|
15798
|
+
}
|
|
15799
|
+
function clearImageBlobs(dataDir) {
|
|
15800
|
+
try {
|
|
15801
|
+
rmSync2(blobDir(dataDir), { recursive: true, force: true });
|
|
15802
|
+
} catch {
|
|
15803
|
+
}
|
|
15804
|
+
}
|
|
15805
|
+
var init_image_blobs = __esm({
|
|
15806
|
+
"src/git/image-blobs.ts"() {
|
|
15807
|
+
"use strict";
|
|
15808
|
+
}
|
|
15809
|
+
});
|
|
15810
|
+
|
|
15728
15811
|
// src/review-notes/types.ts
|
|
15729
15812
|
function isNoteKind(value) {
|
|
15730
15813
|
return NOTE_KINDS.includes(value);
|
|
@@ -15848,18 +15931,18 @@ var init_view = __esm({
|
|
|
15848
15931
|
// src/review-notes/store.ts
|
|
15849
15932
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
15850
15933
|
import { createHash } from "crypto";
|
|
15851
|
-
import { existsSync as
|
|
15852
|
-
import { dirname as dirname3, join as
|
|
15934
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readdirSync, readFileSync as readFileSync6, statSync as statSync2, unlinkSync, writeFileSync as writeFileSync6 } from "fs";
|
|
15935
|
+
import { dirname as dirname3, join as join7 } from "path";
|
|
15853
15936
|
function sanitizeRel(file2) {
|
|
15854
15937
|
const rel = file2.replace(/\\/g, "/").replace(/^\/+/, "").replace(/(^|\/)\.\.(?=\/|$)/g, "$1_");
|
|
15855
15938
|
return rel === "" ? "file" : rel;
|
|
15856
15939
|
}
|
|
15857
15940
|
function shardPath(repoRoot, safeRel, index) {
|
|
15858
|
-
return
|
|
15941
|
+
return join7(repoRoot, NOTES_SUBDIR, `${safeRel}.${String(index).padStart(6, "0")}.sarif`);
|
|
15859
15942
|
}
|
|
15860
15943
|
function listShardIndices(repoRoot, safeRel) {
|
|
15861
|
-
const dir =
|
|
15862
|
-
if (!
|
|
15944
|
+
const dir = join7(repoRoot, NOTES_SUBDIR, dirname3(safeRel));
|
|
15945
|
+
if (!existsSync6(dir)) return [];
|
|
15863
15946
|
const base = safeRel.split("/").pop() ?? safeRel;
|
|
15864
15947
|
const indices = [];
|
|
15865
15948
|
for (const entry of readdirSync(dir)) {
|
|
@@ -15873,7 +15956,7 @@ function totalResults(log) {
|
|
|
15873
15956
|
return log.runs.reduce((sum, run) => sum + run.results.length, 0);
|
|
15874
15957
|
}
|
|
15875
15958
|
function readLog(path) {
|
|
15876
|
-
const raw2 = JSON.parse(
|
|
15959
|
+
const raw2 = JSON.parse(readFileSync6(path, "utf-8"));
|
|
15877
15960
|
const parsed = SarifLogShapeSchema.safeParse(raw2);
|
|
15878
15961
|
if (!parsed.success) {
|
|
15879
15962
|
throw new Error(`existing notes shard is not a SARIF log we recognize, refusing to overwrite: ${path}`);
|
|
@@ -15899,7 +15982,7 @@ function gitValue(repoRoot, args) {
|
|
|
15899
15982
|
}
|
|
15900
15983
|
function anchorSnippet(repoRoot, safeRel, startLine, endLine) {
|
|
15901
15984
|
try {
|
|
15902
|
-
const lines =
|
|
15985
|
+
const lines = readFileSync6(join7(repoRoot, safeRel), "utf-8").split("\n");
|
|
15903
15986
|
const slice = lines.slice(Math.max(0, startLine - 1), Math.max(startLine, endLine));
|
|
15904
15987
|
if (slice.length === 0) return {};
|
|
15905
15988
|
const snippet = slice.join("\n");
|
|
@@ -15937,7 +16020,7 @@ function writeReviewNote(repoRoot, input, opts = {}) {
|
|
|
15937
16020
|
let index = indices.length > 0 ? indices[indices.length - 1] : 0;
|
|
15938
16021
|
let path = shardPath(repoRoot, safeRel, index);
|
|
15939
16022
|
let log;
|
|
15940
|
-
if (
|
|
16023
|
+
if (existsSync6(path)) {
|
|
15941
16024
|
log = readLog(path);
|
|
15942
16025
|
if (totalResults(log) >= cap) {
|
|
15943
16026
|
index += 1;
|
|
@@ -15948,12 +16031,12 @@ function writeReviewNote(repoRoot, input, opts = {}) {
|
|
|
15948
16031
|
log = emptyLog(producer, { producerVersion: input.producerVersion, ...vcs });
|
|
15949
16032
|
}
|
|
15950
16033
|
findOrAddRun(log, producer, input.producerVersion, vcs).results.push(result);
|
|
15951
|
-
|
|
15952
|
-
|
|
16034
|
+
mkdirSync5(dirname3(path), { recursive: true });
|
|
16035
|
+
writeFileSync6(path, JSON.stringify(log, null, 2) + "\n", "utf-8");
|
|
15953
16036
|
return { path, guid: guid3 };
|
|
15954
16037
|
}
|
|
15955
16038
|
function writeLog(path, log) {
|
|
15956
|
-
|
|
16039
|
+
writeFileSync6(path, JSON.stringify(log, null, 2) + "\n", "utf-8");
|
|
15957
16040
|
}
|
|
15958
16041
|
function hashArtifacts(repoRoot, artifacts) {
|
|
15959
16042
|
const out = {};
|
|
@@ -15961,22 +16044,22 @@ function hashArtifacts(repoRoot, artifacts) {
|
|
|
15961
16044
|
const safe = uri.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
15962
16045
|
if (/(^|\/)\.\.(\/|$)/.test(safe)) continue;
|
|
15963
16046
|
try {
|
|
15964
|
-
const abs =
|
|
16047
|
+
const abs = join7(repoRoot, safe);
|
|
15965
16048
|
const stat = statSync2(abs);
|
|
15966
16049
|
if (!stat.isFile() || stat.size > ARTIFACT_HASH_MAX_BYTES) continue;
|
|
15967
|
-
out[uri] = createHash("sha256").update(
|
|
16050
|
+
out[uri] = createHash("sha256").update(readFileSync6(abs)).digest("hex");
|
|
15968
16051
|
} catch {
|
|
15969
16052
|
}
|
|
15970
16053
|
}
|
|
15971
16054
|
return out;
|
|
15972
16055
|
}
|
|
15973
16056
|
function ensureArtifactLfsFilter(repoRoot) {
|
|
15974
|
-
const path =
|
|
16057
|
+
const path = join7(repoRoot, ".gitattributes");
|
|
15975
16058
|
try {
|
|
15976
|
-
const existing =
|
|
16059
|
+
const existing = existsSync6(path) ? readFileSync6(path, "utf-8") : "";
|
|
15977
16060
|
if (existing.includes(".pr-notes/artifacts/**")) return;
|
|
15978
16061
|
const prefix = existing === "" || existing.endsWith("\n") ? "" : "\n";
|
|
15979
|
-
|
|
16062
|
+
writeFileSync6(path, `${existing}${prefix}${LFS_FILTER_LINE}
|
|
15980
16063
|
`, "utf-8");
|
|
15981
16064
|
} catch {
|
|
15982
16065
|
}
|
|
@@ -15985,15 +16068,15 @@ function listShardPaths(repoRoot, safeRel) {
|
|
|
15985
16068
|
return listShardIndices(repoRoot, safeRel).map((i) => shardPath(repoRoot, safeRel, i));
|
|
15986
16069
|
}
|
|
15987
16070
|
function allShardPaths(repoRoot) {
|
|
15988
|
-
const root =
|
|
15989
|
-
if (!
|
|
16071
|
+
const root = join7(repoRoot, NOTES_SUBDIR);
|
|
16072
|
+
if (!existsSync6(root)) return [];
|
|
15990
16073
|
const entries = readdirSync(root, { recursive: true });
|
|
15991
|
-
return entries.filter((e) => SHARD_RE.test(e)).map((e) =>
|
|
16074
|
+
return entries.filter((e) => SHARD_RE.test(e)).map((e) => join7(root, e));
|
|
15992
16075
|
}
|
|
15993
16076
|
function persistOrDelete(path, log) {
|
|
15994
16077
|
log.runs = log.runs.filter((run) => run.results.length > 0);
|
|
15995
16078
|
if (log.runs.length === 0) {
|
|
15996
|
-
if (
|
|
16079
|
+
if (existsSync6(path)) unlinkSync(path);
|
|
15997
16080
|
} else {
|
|
15998
16081
|
writeLog(path, log);
|
|
15999
16082
|
}
|
|
@@ -16080,8 +16163,8 @@ function coalesceFile(repoRoot, file2) {
|
|
|
16080
16163
|
return toRemove.length;
|
|
16081
16164
|
}
|
|
16082
16165
|
function coalesceAll(repoRoot) {
|
|
16083
|
-
const root =
|
|
16084
|
-
if (!
|
|
16166
|
+
const root = join7(repoRoot, NOTES_SUBDIR);
|
|
16167
|
+
if (!existsSync6(root)) return 0;
|
|
16085
16168
|
const sources = /* @__PURE__ */ new Set();
|
|
16086
16169
|
for (const entry of readdirSync(root, { recursive: true })) {
|
|
16087
16170
|
const m = SHARD_RE.exec(entry);
|
|
@@ -16095,10 +16178,10 @@ function readArtifactText(repoRoot, uri) {
|
|
|
16095
16178
|
const safe = uri.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
16096
16179
|
if (/(^|\/)\.\.(\/|$)/.test(safe)) return void 0;
|
|
16097
16180
|
try {
|
|
16098
|
-
const abs =
|
|
16181
|
+
const abs = join7(repoRoot, safe);
|
|
16099
16182
|
const stat = statSync2(abs);
|
|
16100
16183
|
if (!stat.isFile() || stat.size > ARTIFACT_MAX_BYTES) return void 0;
|
|
16101
|
-
const buf =
|
|
16184
|
+
const buf = readFileSync6(abs);
|
|
16102
16185
|
if (buf.includes(0)) return void 0;
|
|
16103
16186
|
return buf.toString("utf-8");
|
|
16104
16187
|
} catch {
|
|
@@ -16161,7 +16244,7 @@ var init_store = __esm({
|
|
|
16161
16244
|
init_sarif();
|
|
16162
16245
|
init_types();
|
|
16163
16246
|
init_view();
|
|
16164
|
-
NOTES_SUBDIR =
|
|
16247
|
+
NOTES_SUBDIR = join7(".pr-notes", "notes");
|
|
16165
16248
|
SHARD_RE = /\.(\d{6})\.sarif$/;
|
|
16166
16249
|
ARTIFACT_HASH_MAX_BYTES = 5e7;
|
|
16167
16250
|
LFS_FILTER_LINE = ".pr-notes/artifacts/** filter=lfs diff=lfs merge=lfs -text";
|
|
@@ -16169,48 +16252,6 @@ var init_store = __esm({
|
|
|
16169
16252
|
}
|
|
16170
16253
|
});
|
|
16171
16254
|
|
|
16172
|
-
// src/difftool/blob-store.ts
|
|
16173
|
-
var blob_store_exports = {};
|
|
16174
|
-
__export(blob_store_exports, {
|
|
16175
|
-
clearDifftoolBlobs: () => clearDifftoolBlobs,
|
|
16176
|
-
readDifftoolBlob: () => readDifftoolBlob,
|
|
16177
|
-
writeDifftoolBlob: () => writeDifftoolBlob
|
|
16178
|
-
});
|
|
16179
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync7, rmSync as rmSync4, writeFileSync as writeFileSync7 } from "fs";
|
|
16180
|
-
import { join as join8 } from "path";
|
|
16181
|
-
function blobDir(dataDir) {
|
|
16182
|
-
return join8(dataDir, "difftool-blobs");
|
|
16183
|
-
}
|
|
16184
|
-
function blobName(fileId, side) {
|
|
16185
|
-
return `${fileId.replace(/[^a-z0-9]/gi, "")}-${side}`;
|
|
16186
|
-
}
|
|
16187
|
-
function writeDifftoolBlob(dataDir, fileId, side, bytes) {
|
|
16188
|
-
if (bytes.length === 0) return;
|
|
16189
|
-
const dir = blobDir(dataDir);
|
|
16190
|
-
mkdirSync6(dir, { recursive: true });
|
|
16191
|
-
writeFileSync7(join8(dir, blobName(fileId, side)), bytes);
|
|
16192
|
-
}
|
|
16193
|
-
function readDifftoolBlob(dataDir, fileId, side) {
|
|
16194
|
-
const path = join8(blobDir(dataDir), blobName(fileId, side));
|
|
16195
|
-
if (!existsSync7(path)) return null;
|
|
16196
|
-
try {
|
|
16197
|
-
return readFileSync7(path);
|
|
16198
|
-
} catch {
|
|
16199
|
-
return null;
|
|
16200
|
-
}
|
|
16201
|
-
}
|
|
16202
|
-
function clearDifftoolBlobs(dataDir) {
|
|
16203
|
-
try {
|
|
16204
|
-
rmSync4(blobDir(dataDir), { recursive: true, force: true });
|
|
16205
|
-
} catch {
|
|
16206
|
-
}
|
|
16207
|
-
}
|
|
16208
|
-
var init_blob_store = __esm({
|
|
16209
|
-
"src/difftool/blob-store.ts"() {
|
|
16210
|
-
"use strict";
|
|
16211
|
-
}
|
|
16212
|
-
});
|
|
16213
|
-
|
|
16214
16255
|
// src/difftool/session.ts
|
|
16215
16256
|
var session_exports = {};
|
|
16216
16257
|
__export(session_exports, {
|
|
@@ -16715,17 +16756,17 @@ __export(difftool_discovery_exports, {
|
|
|
16715
16756
|
tryAcquireStartingLock: () => tryAcquireStartingLock,
|
|
16716
16757
|
writeDiscovery: () => writeDiscovery
|
|
16717
16758
|
});
|
|
16718
|
-
import { existsSync as
|
|
16759
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync12, readFileSync as readFileSync17, rmSync as rmSync5, statSync as statSync4, writeFileSync as writeFileSync12 } from "fs";
|
|
16719
16760
|
import { homedir as homedir4 } from "os";
|
|
16720
|
-
import { join as
|
|
16761
|
+
import { join as join16 } from "path";
|
|
16721
16762
|
function difftoolHome() {
|
|
16722
|
-
return
|
|
16763
|
+
return join16(homedir4(), ".glassbox");
|
|
16723
16764
|
}
|
|
16724
16765
|
function discoveryPath(home = difftoolHome()) {
|
|
16725
|
-
return
|
|
16766
|
+
return join16(home, "difftool.lock");
|
|
16726
16767
|
}
|
|
16727
16768
|
function startingLockPath(home = difftoolHome()) {
|
|
16728
|
-
return
|
|
16769
|
+
return join16(home, "difftool-starting.lock");
|
|
16729
16770
|
}
|
|
16730
16771
|
function parseDiscovery(raw2) {
|
|
16731
16772
|
let parsed;
|
|
@@ -16739,9 +16780,9 @@ function parseDiscovery(raw2) {
|
|
|
16739
16780
|
}
|
|
16740
16781
|
function readDiscovery(home = difftoolHome()) {
|
|
16741
16782
|
const path = discoveryPath(home);
|
|
16742
|
-
if (!
|
|
16783
|
+
if (!existsSync13(path)) return null;
|
|
16743
16784
|
try {
|
|
16744
|
-
return parseDiscovery(
|
|
16785
|
+
return parseDiscovery(readFileSync17(path, "utf-8"));
|
|
16745
16786
|
} catch {
|
|
16746
16787
|
return null;
|
|
16747
16788
|
}
|
|
@@ -16797,9 +16838,9 @@ var init_difftool_discovery = __esm({
|
|
|
16797
16838
|
// src/cli.ts
|
|
16798
16839
|
init_connection();
|
|
16799
16840
|
init_queries();
|
|
16800
|
-
import { existsSync as
|
|
16841
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync13, realpathSync, statSync as statSync5 } from "fs";
|
|
16801
16842
|
import { tmpdir as tmpdir2 } from "os";
|
|
16802
|
-
import { basename as basename2, join as
|
|
16843
|
+
import { basename as basename2, join as join17, resolve as resolve10 } from "path";
|
|
16803
16844
|
|
|
16804
16845
|
// src/debug.ts
|
|
16805
16846
|
var debugEnabled = false;
|
|
@@ -17383,7 +17424,18 @@ async function saveUserPreferences(prefs) {
|
|
|
17383
17424
|
}
|
|
17384
17425
|
|
|
17385
17426
|
// src/demo.ts
|
|
17427
|
+
init_connection();
|
|
17386
17428
|
init_queries();
|
|
17429
|
+
init_image_blobs();
|
|
17430
|
+
function seedSvgBlobs(fileId, diff) {
|
|
17431
|
+
if (!diff.filePath.toLowerCase().endsWith(".svg")) return;
|
|
17432
|
+
const dataDir = getDataDir();
|
|
17433
|
+
if (dataDir === null) return;
|
|
17434
|
+
const lines = diff.hunks.flatMap((h) => h.lines);
|
|
17435
|
+
const sideBytes = (keep) => Buffer.from(lines.filter((l) => keep(l.type)).map((l) => l.content).join("\n"), "utf8");
|
|
17436
|
+
writeImageBlob(dataDir, fileId, "old", sideBytes((t) => t !== "add"));
|
|
17437
|
+
writeImageBlob(dataDir, fileId, "new", sideBytes((t) => t !== "remove"));
|
|
17438
|
+
}
|
|
17387
17439
|
function demoReviewNotes(filePath) {
|
|
17388
17440
|
if (filePath !== "src/auth/session.ts") return [];
|
|
17389
17441
|
return [
|
|
@@ -17655,6 +17707,26 @@ var DEMO_FILES = [
|
|
|
17655
17707
|
status: "modified",
|
|
17656
17708
|
isBinary: true,
|
|
17657
17709
|
hunks: []
|
|
17710
|
+
},
|
|
17711
|
+
{
|
|
17712
|
+
// A real SVG change so the demo (and the e2e suite) exercises the SVG
|
|
17713
|
+
// *rendered* view and its vector zoom (GB-941). Modeled as a rename so both
|
|
17714
|
+
// sides resolve to real committed icons (old = red, new = blue, both 20x20
|
|
17715
|
+
// with explicit width/height so the <img> reports a natural size). Default
|
|
17716
|
+
// view is the code (text) diff; the Rendered toggle switches to ImageDiff.
|
|
17717
|
+
path: "tests/fixtures/diff/new/icon.svg",
|
|
17718
|
+
oldPath: "tests/fixtures/diff/old/icon.svg",
|
|
17719
|
+
status: "modified",
|
|
17720
|
+
hunks: [{
|
|
17721
|
+
oldStart: 1,
|
|
17722
|
+
oldCount: 1,
|
|
17723
|
+
newStart: 1,
|
|
17724
|
+
newCount: 1,
|
|
17725
|
+
lines: [
|
|
17726
|
+
{ type: "remove", oldNum: 1, newNum: null, content: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"><rect width="20" height="20" fill="red"/></svg>' },
|
|
17727
|
+
{ type: "add", oldNum: null, newNum: 1, content: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"><rect width="20" height="20" fill="blue"/></svg>' }
|
|
17728
|
+
]
|
|
17729
|
+
}]
|
|
17658
17730
|
}
|
|
17659
17731
|
];
|
|
17660
17732
|
var GUIDED_NOTES = {
|
|
@@ -17823,6 +17895,7 @@ async function setupDemoReview(scenario) {
|
|
|
17823
17895
|
};
|
|
17824
17896
|
const rf = await addReviewFile(review.id, file2.path, JSON.stringify(diff));
|
|
17825
17897
|
fileIdMap.set(file2.path, rf.id);
|
|
17898
|
+
seedSvgBlobs(rf.id, diff);
|
|
17826
17899
|
}
|
|
17827
17900
|
const { updateFileStatus: updateFileStatus2 } = await Promise.resolve().then(() => (init_queries(), queries_exports));
|
|
17828
17901
|
const reviewedPaths = ["src/utils/password.ts", "src/db/redis.ts", "package.json"];
|
|
@@ -17925,9 +17998,9 @@ async function setupReviewNoteReply(fileIdMap) {
|
|
|
17925
17998
|
}
|
|
17926
17999
|
|
|
17927
18000
|
// src/git/diff.ts
|
|
17928
|
-
import { existsSync as
|
|
18001
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync4, mkdtempSync, readFileSync as readFileSync3, rmSync as rmSync3, statSync, writeFileSync as writeFileSync3 } from "fs";
|
|
17929
18002
|
import { tmpdir } from "os";
|
|
17930
|
-
import { basename, dirname, join as
|
|
18003
|
+
import { basename, dirname, join as join4, resolve } from "path";
|
|
17931
18004
|
|
|
17932
18005
|
// src/git/repo.ts
|
|
17933
18006
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
@@ -18082,16 +18155,16 @@ function getDirectComparisonFiles(mode, cwd) {
|
|
|
18082
18155
|
function diffRawContent(displayPath, oldContent, newContent) {
|
|
18083
18156
|
const rel = displayPath.replace(/\\/g, "/").replace(/^\/+/, "").replace(/(^|\/)\.\.(?=\/|$)/g, "$1_");
|
|
18084
18157
|
const safeRel = rel === "" ? "file" : rel;
|
|
18085
|
-
const work = mkdtempSync(
|
|
18086
|
-
const rootA =
|
|
18087
|
-
const rootB =
|
|
18088
|
-
const oldAbs =
|
|
18089
|
-
const newAbs =
|
|
18158
|
+
const work = mkdtempSync(join4(tmpdir(), "glassbox-difftool-append-"));
|
|
18159
|
+
const rootA = join4(work, "a");
|
|
18160
|
+
const rootB = join4(work, "b");
|
|
18161
|
+
const oldAbs = join4(rootA, safeRel);
|
|
18162
|
+
const newAbs = join4(rootB, safeRel);
|
|
18090
18163
|
try {
|
|
18091
|
-
|
|
18092
|
-
|
|
18093
|
-
|
|
18094
|
-
|
|
18164
|
+
mkdirSync4(dirname(oldAbs), { recursive: true });
|
|
18165
|
+
mkdirSync4(dirname(newAbs), { recursive: true });
|
|
18166
|
+
writeFileSync3(oldAbs, oldContent);
|
|
18167
|
+
writeFileSync3(newAbs, newContent);
|
|
18095
18168
|
const rawDiff = gitOrEmpty(["diff", "--no-index", "-U3", toGitArg(oldAbs), toGitArg(newAbs)], work);
|
|
18096
18169
|
const diffs = normalizeDiffPaths(parseDiff(rawDiff), rootA, rootB);
|
|
18097
18170
|
if (diffs.length === 0) {
|
|
@@ -18104,7 +18177,7 @@ function diffRawContent(displayPath, oldContent, newContent) {
|
|
|
18104
18177
|
return { ...diff, filePath: safeRel, oldPath: null, status };
|
|
18105
18178
|
} finally {
|
|
18106
18179
|
try {
|
|
18107
|
-
|
|
18180
|
+
rmSync3(work, { recursive: true, force: true });
|
|
18108
18181
|
} catch {
|
|
18109
18182
|
}
|
|
18110
18183
|
}
|
|
@@ -18139,7 +18212,7 @@ function getAllFiles(repoRoot) {
|
|
|
18139
18212
|
function createNewFileDiff(filePath, repoRoot) {
|
|
18140
18213
|
let content;
|
|
18141
18214
|
try {
|
|
18142
|
-
const buf =
|
|
18215
|
+
const buf = readFileSync3(resolve(repoRoot, filePath));
|
|
18143
18216
|
const checkLen = Math.min(buf.length, 8192);
|
|
18144
18217
|
for (let i = 0; i < checkLen; i++) {
|
|
18145
18218
|
if (buf[i] === 0) {
|
|
@@ -18259,7 +18332,7 @@ function getFileContent(filePath, ref, cwd) {
|
|
|
18259
18332
|
const repoRoot = getRepoRoot(cwd);
|
|
18260
18333
|
try {
|
|
18261
18334
|
if (ref === "working") {
|
|
18262
|
-
return
|
|
18335
|
+
return readFileSync3(resolve(repoRoot, filePath), "utf-8");
|
|
18263
18336
|
}
|
|
18264
18337
|
return git(["show", `${ref}:${filePath}`], repoRoot);
|
|
18265
18338
|
} catch {
|
|
@@ -18269,9 +18342,9 @@ function getFileContent(filePath, ref, cwd) {
|
|
|
18269
18342
|
function getModeFileContent(mode, filePath, side, cwd) {
|
|
18270
18343
|
if (mode.type === "diff") {
|
|
18271
18344
|
const { rootA, rootB } = directComparisonRoots(mode);
|
|
18272
|
-
const abs =
|
|
18345
|
+
const abs = join4(side === "old" ? rootA : rootB, filePath);
|
|
18273
18346
|
try {
|
|
18274
|
-
return
|
|
18347
|
+
return readFileSync3(abs, "utf-8");
|
|
18275
18348
|
} catch {
|
|
18276
18349
|
return "";
|
|
18277
18350
|
}
|
|
@@ -18307,9 +18380,9 @@ function getSingleFileDiff(mode, filePath, repoRoot, extraFlags = "") {
|
|
|
18307
18380
|
}
|
|
18308
18381
|
if (mode.type === "diff") {
|
|
18309
18382
|
const { rootA, rootB } = directComparisonRoots(mode);
|
|
18310
|
-
const oldAbs =
|
|
18311
|
-
const newAbs =
|
|
18312
|
-
if (!
|
|
18383
|
+
const oldAbs = join4(rootA, filePath);
|
|
18384
|
+
const newAbs = join4(rootB, filePath);
|
|
18385
|
+
if (!existsSync3(oldAbs) || !existsSync3(newAbs)) return null;
|
|
18313
18386
|
const args2 = ["diff", "--no-index", "-U3"];
|
|
18314
18387
|
if (extraFlags) args2.push(...extraFlags.split(" ").filter(Boolean));
|
|
18315
18388
|
args2.push(toGitArg(oldAbs), toGitArg(newAbs));
|
|
@@ -18371,15 +18444,15 @@ function getModeArgs(mode) {
|
|
|
18371
18444
|
|
|
18372
18445
|
// src/lock.ts
|
|
18373
18446
|
init_zod();
|
|
18374
|
-
import { existsSync as
|
|
18375
|
-
import { join as
|
|
18447
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
18448
|
+
import { join as join5 } from "path";
|
|
18376
18449
|
var lockPath = null;
|
|
18377
18450
|
var LockFileSchema = external_exports.object({ pid: external_exports.number().int() });
|
|
18378
18451
|
function acquireLock(dataDir) {
|
|
18379
|
-
lockPath =
|
|
18380
|
-
if (
|
|
18452
|
+
lockPath = join5(dataDir, "glassbox.lock");
|
|
18453
|
+
if (existsSync4(lockPath)) {
|
|
18381
18454
|
try {
|
|
18382
|
-
const raw2 = JSON.parse(
|
|
18455
|
+
const raw2 = JSON.parse(readFileSync4(lockPath, "utf-8"));
|
|
18383
18456
|
const contents = LockFileSchema.parse(raw2);
|
|
18384
18457
|
const pid = contents.pid;
|
|
18385
18458
|
try {
|
|
@@ -18392,13 +18465,13 @@ function acquireLock(dataDir) {
|
|
|
18392
18465
|
process.exit(1);
|
|
18393
18466
|
} catch {
|
|
18394
18467
|
console.log(` Removing stale lock from PID ${pid}`);
|
|
18395
|
-
|
|
18468
|
+
rmSync4(lockPath, { force: true });
|
|
18396
18469
|
}
|
|
18397
18470
|
} catch {
|
|
18398
|
-
|
|
18471
|
+
rmSync4(lockPath, { force: true });
|
|
18399
18472
|
}
|
|
18400
18473
|
}
|
|
18401
|
-
|
|
18474
|
+
writeFileSync4(lockPath, JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
18402
18475
|
const cleanup = () => {
|
|
18403
18476
|
releaseLock();
|
|
18404
18477
|
};
|
|
@@ -18415,7 +18488,7 @@ function acquireLock(dataDir) {
|
|
|
18415
18488
|
function releaseLock() {
|
|
18416
18489
|
if (lockPath !== null) {
|
|
18417
18490
|
try {
|
|
18418
|
-
|
|
18491
|
+
rmSync4(lockPath, { force: true });
|
|
18419
18492
|
} catch {
|
|
18420
18493
|
}
|
|
18421
18494
|
lockPath = null;
|
|
@@ -18528,15 +18601,15 @@ async function updateReviewDiffs(reviewId, newDiffs, headCommit) {
|
|
|
18528
18601
|
|
|
18529
18602
|
// src/server.ts
|
|
18530
18603
|
import { serve } from "@hono/node-server";
|
|
18531
|
-
import { existsSync as
|
|
18604
|
+
import { existsSync as existsSync10, readFileSync as readFileSync14 } from "fs";
|
|
18532
18605
|
import { Hono as Hono19 } from "hono";
|
|
18533
|
-
import { dirname as dirname4, join as
|
|
18606
|
+
import { dirname as dirname4, join as join13 } from "path";
|
|
18534
18607
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
18535
18608
|
|
|
18536
18609
|
// src/channel-config.ts
|
|
18537
18610
|
init_zod();
|
|
18538
|
-
import { existsSync as
|
|
18539
|
-
import { dirname as dirname2, join as
|
|
18611
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
18612
|
+
import { dirname as dirname2, join as join6, resolve as resolve2 } from "path";
|
|
18540
18613
|
import { fileURLToPath } from "url";
|
|
18541
18614
|
var MCP_SERVER_KEY = "glassbox-channel";
|
|
18542
18615
|
var McpConfigSchema = external_exports.object({
|
|
@@ -18546,11 +18619,11 @@ var HealthResponseSchema = external_exports.object({ ok: external_exports.boolea
|
|
|
18546
18619
|
function getChannelServerPath() {
|
|
18547
18620
|
const thisDir = dirname2(fileURLToPath(import.meta.url));
|
|
18548
18621
|
const distPath = resolve2(thisDir, "channel.js");
|
|
18549
|
-
if (
|
|
18622
|
+
if (existsSync5(distPath)) {
|
|
18550
18623
|
return { command: process.execPath, args: [distPath] };
|
|
18551
18624
|
}
|
|
18552
18625
|
const srcPath = resolve2(thisDir, "channel.ts");
|
|
18553
|
-
if (
|
|
18626
|
+
if (existsSync5(srcPath)) {
|
|
18554
18627
|
return { command: "npx", args: ["tsx", srcPath] };
|
|
18555
18628
|
}
|
|
18556
18629
|
return { command: process.execPath, args: [distPath] };
|
|
@@ -18560,12 +18633,12 @@ function projectRoot(dataDir) {
|
|
|
18560
18633
|
}
|
|
18561
18634
|
function registerChannel(dataDir) {
|
|
18562
18635
|
const root = projectRoot(dataDir);
|
|
18563
|
-
const mcpPath =
|
|
18636
|
+
const mcpPath = join6(root, ".mcp.json");
|
|
18564
18637
|
const { command, args } = getChannelServerPath();
|
|
18565
18638
|
let config2 = {};
|
|
18566
|
-
if (
|
|
18639
|
+
if (existsSync5(mcpPath)) {
|
|
18567
18640
|
try {
|
|
18568
|
-
const raw2 = JSON.parse(
|
|
18641
|
+
const raw2 = JSON.parse(readFileSync5(mcpPath, "utf-8"));
|
|
18569
18642
|
const parsed = McpConfigSchema.safeParse(raw2);
|
|
18570
18643
|
if (parsed.success) config2 = parsed.data;
|
|
18571
18644
|
} catch {
|
|
@@ -18576,14 +18649,14 @@ function registerChannel(dataDir) {
|
|
|
18576
18649
|
command,
|
|
18577
18650
|
args: [...args, "--data-dir", dataDir]
|
|
18578
18651
|
};
|
|
18579
|
-
|
|
18652
|
+
writeFileSync5(mcpPath, JSON.stringify(config2, null, 2) + "\n", "utf-8");
|
|
18580
18653
|
}
|
|
18581
18654
|
function unregisterChannel(dataDir) {
|
|
18582
18655
|
const root = projectRoot(dataDir);
|
|
18583
|
-
const mcpPath =
|
|
18584
|
-
if (!
|
|
18656
|
+
const mcpPath = join6(root, ".mcp.json");
|
|
18657
|
+
if (!existsSync5(mcpPath)) return;
|
|
18585
18658
|
try {
|
|
18586
|
-
const raw2 = JSON.parse(
|
|
18659
|
+
const raw2 = JSON.parse(readFileSync5(mcpPath, "utf-8"));
|
|
18587
18660
|
const parsed = McpConfigSchema.safeParse(raw2);
|
|
18588
18661
|
if (!parsed.success) return;
|
|
18589
18662
|
const config2 = parsed.data;
|
|
@@ -18591,14 +18664,14 @@ function unregisterChannel(dataDir) {
|
|
|
18591
18664
|
const servers = { ...config2.mcpServers };
|
|
18592
18665
|
delete servers[MCP_SERVER_KEY];
|
|
18593
18666
|
config2.mcpServers = servers;
|
|
18594
|
-
|
|
18667
|
+
writeFileSync5(mcpPath, JSON.stringify(config2, null, 2) + "\n", "utf-8");
|
|
18595
18668
|
}
|
|
18596
18669
|
} catch {
|
|
18597
18670
|
}
|
|
18598
18671
|
}
|
|
18599
18672
|
function getChannelPort(dataDir) {
|
|
18600
18673
|
try {
|
|
18601
|
-
const portStr =
|
|
18674
|
+
const portStr = readFileSync5(join6(dataDir, "channel-port"), "utf-8").trim();
|
|
18602
18675
|
const port = parseInt(portStr, 10);
|
|
18603
18676
|
return isNaN(port) ? null : port;
|
|
18604
18677
|
} catch {
|
|
@@ -19887,6 +19960,7 @@ __export(annotations_exports, {
|
|
|
19887
19960
|
DeleteAnnotationReqSchema: () => DeleteAnnotationReqSchema,
|
|
19888
19961
|
DeleteAnnotationRespSchema: () => DeleteAnnotationRespSchema,
|
|
19889
19962
|
DeleteStaleAnnotationsRespSchema: () => DeleteStaleAnnotationsRespSchema,
|
|
19963
|
+
ImageRegionSchema: () => ImageRegionSchema,
|
|
19890
19964
|
KeepAllStaleAnnotationsRespSchema: () => KeepAllStaleAnnotationsRespSchema,
|
|
19891
19965
|
KeepAnnotationReqSchema: () => KeepAnnotationReqSchema,
|
|
19892
19966
|
KeepAnnotationRespSchema: () => KeepAnnotationRespSchema,
|
|
@@ -19897,6 +19971,9 @@ __export(annotations_exports, {
|
|
|
19897
19971
|
UpdateAnnotationBodySchema: () => UpdateAnnotationBodySchema,
|
|
19898
19972
|
UpdateAnnotationReqSchema: () => UpdateAnnotationReqSchema,
|
|
19899
19973
|
UpdateAnnotationRespSchema: () => UpdateAnnotationRespSchema,
|
|
19974
|
+
UpdateRegionBodySchema: () => UpdateRegionBodySchema,
|
|
19975
|
+
UpdateRegionReqSchema: () => UpdateRegionReqSchema,
|
|
19976
|
+
UpdateRegionRespSchema: () => UpdateRegionRespSchema,
|
|
19900
19977
|
createAnnotation: () => createAnnotation,
|
|
19901
19978
|
deleteAnnotation: () => deleteAnnotation2,
|
|
19902
19979
|
deleteStaleAnnotations: () => deleteStaleAnnotations2,
|
|
@@ -19904,7 +19981,8 @@ __export(annotations_exports, {
|
|
|
19904
19981
|
keepAnnotation: () => keepAnnotation,
|
|
19905
19982
|
listAllAnnotations: () => listAllAnnotations,
|
|
19906
19983
|
moveAnnotation: () => moveAnnotation2,
|
|
19907
|
-
updateAnnotation: () => updateAnnotation2
|
|
19984
|
+
updateAnnotation: () => updateAnnotation2,
|
|
19985
|
+
updateAnnotationRegion: () => updateAnnotationRegion2
|
|
19908
19986
|
});
|
|
19909
19987
|
init_zod();
|
|
19910
19988
|
init_schemas3();
|
|
@@ -19920,12 +19998,17 @@ var AnnotationCategorySchema = external_exports.enum([
|
|
|
19920
19998
|
var AnnotationSideSchema = external_exports.enum(["old", "new"]);
|
|
19921
19999
|
var CreateAnnotationReqSchema = external_exports.object({
|
|
19922
20000
|
reviewFileId: external_exports.string().min(1),
|
|
19923
|
-
|
|
20001
|
+
// `0` creates an image-level annotation (doc 23): a general image comment, or
|
|
20002
|
+
// — when `region` is set — a comment anchored to a rectangle on the image.
|
|
20003
|
+
// Line-anchored annotations pass the real 1-based line number.
|
|
20004
|
+
lineNumber: external_exports.number().int().min(0),
|
|
19924
20005
|
side: AnnotationSideSchema,
|
|
19925
20006
|
category: AnnotationCategorySchema,
|
|
19926
20007
|
content: external_exports.string().min(1),
|
|
19927
20008
|
/** SARIF guid of the AI review note this annotation replies to (doc 20 threading). */
|
|
19928
|
-
replyToNoteId: external_exports.string().optional()
|
|
20009
|
+
replyToNoteId: external_exports.string().optional(),
|
|
20010
|
+
/** Normalized rectangle for an image-region annotation (doc 23). */
|
|
20011
|
+
region: ImageRegionSchema.optional()
|
|
19929
20012
|
});
|
|
19930
20013
|
var CreateAnnotationRespSchema = AnnotationSchema;
|
|
19931
20014
|
var UpdateAnnotationReqSchema = external_exports.object({
|
|
@@ -19944,6 +20027,12 @@ var MoveAnnotationReqSchema = external_exports.object({
|
|
|
19944
20027
|
});
|
|
19945
20028
|
var MoveAnnotationBodySchema = MoveAnnotationReqSchema.omit({ id: true });
|
|
19946
20029
|
var MoveAnnotationRespSchema = OkResponseSchema;
|
|
20030
|
+
var UpdateRegionReqSchema = external_exports.object({
|
|
20031
|
+
id: external_exports.string(),
|
|
20032
|
+
region: ImageRegionSchema
|
|
20033
|
+
});
|
|
20034
|
+
var UpdateRegionBodySchema = UpdateRegionReqSchema.omit({ id: true });
|
|
20035
|
+
var UpdateRegionRespSchema = OkResponseSchema;
|
|
19947
20036
|
var KeepAnnotationReqSchema = external_exports.object({ id: external_exports.string() });
|
|
19948
20037
|
var KeepAnnotationRespSchema = OkResponseSchema;
|
|
19949
20038
|
var DeleteStaleAnnotationsRespSchema = OkResponseSchema;
|
|
@@ -19965,6 +20054,10 @@ async function moveAnnotation2(req) {
|
|
|
19965
20054
|
const { id, ...body } = req;
|
|
19966
20055
|
return apiCall(MoveAnnotationRespSchema, `/annotations/${id}/move`, { method: "PATCH", body });
|
|
19967
20056
|
}
|
|
20057
|
+
async function updateAnnotationRegion2(req) {
|
|
20058
|
+
const { id, ...body } = req;
|
|
20059
|
+
return apiCall(UpdateRegionRespSchema, `/annotations/${id}/region`, { method: "PATCH", body });
|
|
20060
|
+
}
|
|
19968
20061
|
async function keepAnnotation(req) {
|
|
19969
20062
|
return apiCall(KeepAnnotationRespSchema, `/annotations/${req.id}/keep`, { method: "POST" });
|
|
19970
20063
|
}
|
|
@@ -21666,25 +21759,26 @@ init_queries();
|
|
|
21666
21759
|
// src/export/generate.ts
|
|
21667
21760
|
init_zod();
|
|
21668
21761
|
init_queries();
|
|
21762
|
+
init_schemas3();
|
|
21669
21763
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
21670
|
-
import { appendFileSync, existsSync as
|
|
21764
|
+
import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync7, unlinkSync as unlinkSync2, writeFileSync as writeFileSync7 } from "fs";
|
|
21671
21765
|
import { homedir as homedir2 } from "os";
|
|
21672
|
-
import { join as
|
|
21673
|
-
var DISMISS_FILE =
|
|
21766
|
+
import { join as join8 } from "path";
|
|
21767
|
+
var DISMISS_FILE = join8(homedir2(), ".glassbox", "gitignore-dismissed.json");
|
|
21674
21768
|
var DISMISS_DAYS = 30;
|
|
21675
21769
|
var DismissalsSchema = external_exports.record(external_exports.string(), external_exports.number());
|
|
21676
21770
|
function loadDismissals() {
|
|
21677
21771
|
try {
|
|
21678
|
-
const parsed = DismissalsSchema.safeParse(JSON.parse(
|
|
21772
|
+
const parsed = DismissalsSchema.safeParse(JSON.parse(readFileSync7(DISMISS_FILE, "utf-8")));
|
|
21679
21773
|
return parsed.success ? parsed.data : {};
|
|
21680
21774
|
} catch {
|
|
21681
21775
|
return {};
|
|
21682
21776
|
}
|
|
21683
21777
|
}
|
|
21684
21778
|
function saveDismissals(data) {
|
|
21685
|
-
const dir =
|
|
21686
|
-
|
|
21687
|
-
|
|
21779
|
+
const dir = join8(homedir2(), ".glassbox");
|
|
21780
|
+
mkdirSync6(dir, { recursive: true });
|
|
21781
|
+
writeFileSync7(DISMISS_FILE, JSON.stringify(data), "utf-8");
|
|
21688
21782
|
}
|
|
21689
21783
|
function isGlassboxGitignored(repoRoot) {
|
|
21690
21784
|
const result = spawnSync6("git", ["check-ignore", "-q", ".glassbox"], { cwd: repoRoot, stdio: "pipe" });
|
|
@@ -21702,16 +21796,16 @@ function shouldPromptGitignore(repoRoot) {
|
|
|
21702
21796
|
return true;
|
|
21703
21797
|
}
|
|
21704
21798
|
function addGlassboxToGitignore(repoRoot) {
|
|
21705
|
-
const gitignorePath =
|
|
21706
|
-
if (
|
|
21707
|
-
const content =
|
|
21799
|
+
const gitignorePath = join8(repoRoot, ".gitignore");
|
|
21800
|
+
if (existsSync7(gitignorePath)) {
|
|
21801
|
+
const content = readFileSync7(gitignorePath, "utf-8");
|
|
21708
21802
|
if (!content.endsWith("\n")) {
|
|
21709
21803
|
appendFileSync(gitignorePath, "\n.glassbox/\n", "utf-8");
|
|
21710
21804
|
} else {
|
|
21711
21805
|
appendFileSync(gitignorePath, ".glassbox/\n", "utf-8");
|
|
21712
21806
|
}
|
|
21713
21807
|
} else {
|
|
21714
|
-
|
|
21808
|
+
writeFileSync7(gitignorePath, ".glassbox/\n", "utf-8");
|
|
21715
21809
|
}
|
|
21716
21810
|
}
|
|
21717
21811
|
function dismissGitignorePrompt2(repoRoot) {
|
|
@@ -21720,17 +21814,30 @@ function dismissGitignorePrompt2(repoRoot) {
|
|
|
21720
21814
|
saveDismissals(dismissals);
|
|
21721
21815
|
}
|
|
21722
21816
|
function deleteReviewExport(reviewId, repoRoot) {
|
|
21723
|
-
const exportDir =
|
|
21724
|
-
const archivePath =
|
|
21725
|
-
if (
|
|
21817
|
+
const exportDir = join8(repoRoot, ".glassbox");
|
|
21818
|
+
const archivePath = join8(exportDir, `review-${reviewId}.md`);
|
|
21819
|
+
if (existsSync7(archivePath)) unlinkSync2(archivePath);
|
|
21820
|
+
}
|
|
21821
|
+
function annotationAnchorLabel(a) {
|
|
21822
|
+
if (a.line_number !== 0) return `**Line ${a.line_number}**`;
|
|
21823
|
+
if (a.region_data !== null) {
|
|
21824
|
+
const parsed = ImageRegionSchema.safeParse(JSON.parse(a.region_data));
|
|
21825
|
+
if (parsed.success) {
|
|
21826
|
+
const { x, y, w, h, side } = parsed.data;
|
|
21827
|
+
const pct = (n) => `${Math.round(n * 100)}%`;
|
|
21828
|
+
const scope = side === "old" ? ", A image only" : side === "new" ? ", B image only" : "";
|
|
21829
|
+
return `**Image region (${pct(x)}, ${pct(y)}, ${pct(w)}\xD7${pct(h)}${scope})**`;
|
|
21830
|
+
}
|
|
21831
|
+
}
|
|
21832
|
+
return "**Image comment**";
|
|
21726
21833
|
}
|
|
21727
21834
|
async function generateReviewExport(reviewId, repoRoot, isCurrent) {
|
|
21728
21835
|
const review = await getReview(reviewId);
|
|
21729
21836
|
if (!review) throw new Error("Review not found");
|
|
21730
21837
|
const files = await getReviewFiles(reviewId);
|
|
21731
21838
|
const annotations = await getAnnotationsForReview(reviewId);
|
|
21732
|
-
const exportDir =
|
|
21733
|
-
|
|
21839
|
+
const exportDir = join8(repoRoot, ".glassbox");
|
|
21840
|
+
mkdirSync6(exportDir, { recursive: true });
|
|
21734
21841
|
const byFile = {};
|
|
21735
21842
|
for (const a of annotations) {
|
|
21736
21843
|
if (!(a.file_path in byFile)) byFile[a.file_path] = [];
|
|
@@ -21766,7 +21873,8 @@ async function generateReviewExport(reviewId, repoRoot, isCurrent) {
|
|
|
21766
21873
|
lines.push("> project configuration (CLAUDE.md, .cursorrules, etc.) with these preferences/rules.");
|
|
21767
21874
|
lines.push("");
|
|
21768
21875
|
for (const item of rememberItems) {
|
|
21769
|
-
|
|
21876
|
+
const anchor = item.line_number === 0 ? `${item.file_path} (image)` : `${item.file_path}:${item.line_number}`;
|
|
21877
|
+
lines.push(`- **${anchor}** - ${item.content}`);
|
|
21770
21878
|
}
|
|
21771
21879
|
lines.push("");
|
|
21772
21880
|
}
|
|
@@ -21777,7 +21885,7 @@ async function generateReviewExport(reviewId, repoRoot, isCurrent) {
|
|
|
21777
21885
|
lines.push(`### ${filePath}`);
|
|
21778
21886
|
lines.push("");
|
|
21779
21887
|
for (const a of fileAnns) {
|
|
21780
|
-
lines.push(`-
|
|
21888
|
+
lines.push(`- ${annotationAnchorLabel(a)} [${a.category}]: ${a.content}`);
|
|
21781
21889
|
}
|
|
21782
21890
|
lines.push("");
|
|
21783
21891
|
}
|
|
@@ -21797,11 +21905,11 @@ async function generateReviewExport(reviewId, repoRoot, isCurrent) {
|
|
|
21797
21905
|
lines.push("6. **note** annotations are informational context. Consider them but they may not require code changes.");
|
|
21798
21906
|
lines.push("");
|
|
21799
21907
|
const content = lines.join("\n");
|
|
21800
|
-
const archivePath =
|
|
21801
|
-
|
|
21908
|
+
const archivePath = join8(exportDir, `review-${review.id}.md`);
|
|
21909
|
+
writeFileSync7(archivePath, content, "utf-8");
|
|
21802
21910
|
if (isCurrent) {
|
|
21803
|
-
const latestPath =
|
|
21804
|
-
|
|
21911
|
+
const latestPath = join8(exportDir, "latest-review.md");
|
|
21912
|
+
writeFileSync7(latestPath, content, "utf-8");
|
|
21805
21913
|
return latestPath;
|
|
21806
21914
|
}
|
|
21807
21915
|
return archivePath;
|
|
@@ -21833,7 +21941,8 @@ annotationsRoutes.post("/annotations", async (c) => {
|
|
|
21833
21941
|
body.side,
|
|
21834
21942
|
body.category,
|
|
21835
21943
|
body.content,
|
|
21836
|
-
body.replyToNoteId
|
|
21944
|
+
body.replyToNoteId,
|
|
21945
|
+
body.region
|
|
21837
21946
|
);
|
|
21838
21947
|
autoExport(c);
|
|
21839
21948
|
return c.json(annotation, 201);
|
|
@@ -21854,6 +21963,15 @@ annotationsRoutes.delete("/annotations/:id", async (c) => {
|
|
|
21854
21963
|
autoExport(c);
|
|
21855
21964
|
return c.json({ ok: true });
|
|
21856
21965
|
});
|
|
21966
|
+
annotationsRoutes.patch("/annotations/:id/region", async (c) => {
|
|
21967
|
+
const id = requirePathParam(c, "id");
|
|
21968
|
+
if (!id.ok) return id.response;
|
|
21969
|
+
const parsed = await parseBody(c, UpdateRegionBodySchema);
|
|
21970
|
+
if (!parsed.ok) return parsed.response;
|
|
21971
|
+
await updateAnnotationRegion(id.data, parsed.data.region);
|
|
21972
|
+
autoExport(c);
|
|
21973
|
+
return c.json({ ok: true });
|
|
21974
|
+
});
|
|
21857
21975
|
annotationsRoutes.patch("/annotations/:id/move", async (c) => {
|
|
21858
21976
|
const id = requirePathParam(c, "id");
|
|
21859
21977
|
if (!id.ok) return id.response;
|
|
@@ -22026,7 +22144,6 @@ filesRoutes.post("/files/:fileId/open", async (c) => {
|
|
|
22026
22144
|
// src/routes/api/image.ts
|
|
22027
22145
|
init_connection();
|
|
22028
22146
|
init_queries();
|
|
22029
|
-
init_blob_store();
|
|
22030
22147
|
import { Hono as Hono7 } from "hono";
|
|
22031
22148
|
|
|
22032
22149
|
// src/git/image.ts
|
|
@@ -22355,255 +22472,22 @@ function getNewImage(mode, filePath, repoRoot) {
|
|
|
22355
22472
|
return { data, size: data.length };
|
|
22356
22473
|
}
|
|
22357
22474
|
|
|
22358
|
-
// src/git/svg-rasterize.ts
|
|
22359
|
-
import { Worker } from "worker_threads";
|
|
22360
|
-
|
|
22361
|
-
// src/git/svg-rasterize-render.ts
|
|
22362
|
-
import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
|
|
22363
|
-
import { createRequire } from "module";
|
|
22364
|
-
import { join as join10 } from "path";
|
|
22365
|
-
var initialized = false;
|
|
22366
|
-
var ResvgClass;
|
|
22367
|
-
var fontBuffers = [];
|
|
22368
|
-
async function ensureRenderInit() {
|
|
22369
|
-
if (initialized) return;
|
|
22370
|
-
const require2 = createRequire(import.meta.url);
|
|
22371
|
-
const resvgPath = require2.resolve("@resvg/resvg-wasm");
|
|
22372
|
-
const wasmPath = resvgPath.replace(/index\.(js|mjs)$/, "index_bg.wasm");
|
|
22373
|
-
const wasmBuffer = readFileSync9(wasmPath);
|
|
22374
|
-
const mod = await import("@resvg/resvg-wasm");
|
|
22375
|
-
await mod.initWasm(wasmBuffer);
|
|
22376
|
-
ResvgClass = mod.Resvg;
|
|
22377
|
-
fontBuffers = loadSystemFonts();
|
|
22378
|
-
initialized = true;
|
|
22379
|
-
}
|
|
22380
|
-
function loadSystemFonts() {
|
|
22381
|
-
const buffers = [];
|
|
22382
|
-
const candidates = getFontCandidates();
|
|
22383
|
-
for (const path of candidates) {
|
|
22384
|
-
if (!existsSync8(path)) continue;
|
|
22385
|
-
try {
|
|
22386
|
-
buffers.push(readFileSync9(path));
|
|
22387
|
-
} catch {
|
|
22388
|
-
}
|
|
22389
|
-
}
|
|
22390
|
-
return buffers;
|
|
22391
|
-
}
|
|
22392
|
-
function getFontCandidates() {
|
|
22393
|
-
const os = process.platform;
|
|
22394
|
-
if (os === "darwin") {
|
|
22395
|
-
const sys = "/System/Library/Fonts";
|
|
22396
|
-
const sup = "/System/Library/Fonts/Supplemental";
|
|
22397
|
-
return [
|
|
22398
|
-
// Core system fonts (serif, sans-serif, monospace)
|
|
22399
|
-
join10(sys, "Helvetica.ttc"),
|
|
22400
|
-
join10(sys, "Times.ttc"),
|
|
22401
|
-
join10(sys, "Courier.ttc"),
|
|
22402
|
-
join10(sys, "Menlo.ttc"),
|
|
22403
|
-
join10(sys, "SFPro.ttf"),
|
|
22404
|
-
join10(sys, "SFNS.ttf"),
|
|
22405
|
-
join10(sys, "SFNSMono.ttf"),
|
|
22406
|
-
// Supplemental (common named fonts in SVGs)
|
|
22407
|
-
join10(sup, "Arial.ttf"),
|
|
22408
|
-
join10(sup, "Arial Bold.ttf"),
|
|
22409
|
-
join10(sup, "Georgia.ttf"),
|
|
22410
|
-
join10(sup, "Verdana.ttf"),
|
|
22411
|
-
join10(sup, "Tahoma.ttf"),
|
|
22412
|
-
join10(sup, "Trebuchet MS.ttf"),
|
|
22413
|
-
join10(sup, "Impact.ttf"),
|
|
22414
|
-
join10(sup, "Comic Sans MS.ttf"),
|
|
22415
|
-
join10(sup, "Courier New.ttf"),
|
|
22416
|
-
join10(sup, "Times New Roman.ttf")
|
|
22417
|
-
];
|
|
22418
|
-
}
|
|
22419
|
-
if (os === "linux") {
|
|
22420
|
-
return [
|
|
22421
|
-
// DejaVu (most common Linux fallback)
|
|
22422
|
-
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
|
22423
|
-
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
|
|
22424
|
-
"/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf",
|
|
22425
|
-
"/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
|
|
22426
|
-
// Liberation (metric-compatible with Arial/Times/Courier)
|
|
22427
|
-
"/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
|
|
22428
|
-
"/usr/share/fonts/truetype/liberation/LiberationSerif-Regular.ttf",
|
|
22429
|
-
"/usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf",
|
|
22430
|
-
// Noto (common on modern distros)
|
|
22431
|
-
"/usr/share/fonts/truetype/noto/NotoSans-Regular.ttf"
|
|
22432
|
-
];
|
|
22433
|
-
}
|
|
22434
|
-
if (os === "win32") {
|
|
22435
|
-
const winFonts = join10(process.env.WINDIR ?? "C:\\Windows", "Fonts");
|
|
22436
|
-
return [
|
|
22437
|
-
join10(winFonts, "arial.ttf"),
|
|
22438
|
-
join10(winFonts, "arialbd.ttf"),
|
|
22439
|
-
join10(winFonts, "times.ttf"),
|
|
22440
|
-
join10(winFonts, "cour.ttf"),
|
|
22441
|
-
join10(winFonts, "verdana.ttf"),
|
|
22442
|
-
join10(winFonts, "tahoma.ttf"),
|
|
22443
|
-
join10(winFonts, "georgia.ttf"),
|
|
22444
|
-
join10(winFonts, "consola.ttf"),
|
|
22445
|
-
join10(winFonts, "segoeui.ttf")
|
|
22446
|
-
];
|
|
22447
|
-
}
|
|
22448
|
-
return [];
|
|
22449
|
-
}
|
|
22450
|
-
function parseSvgDimensions(svg) {
|
|
22451
|
-
const widthMatch = svg.match(/\bwidth\s*=\s*["']([^"']+)["']/);
|
|
22452
|
-
const heightMatch = svg.match(/\bheight\s*=\s*["']([^"']+)["']/);
|
|
22453
|
-
const viewBoxMatch = svg.match(/\bviewBox\s*=\s*["']([^"']+)["']/);
|
|
22454
|
-
let width = widthMatch ? parseFloat(widthMatch[1]) : NaN;
|
|
22455
|
-
let height = heightMatch ? parseFloat(heightMatch[1]) : NaN;
|
|
22456
|
-
if ((isNaN(width) || isNaN(height)) && viewBoxMatch) {
|
|
22457
|
-
const parts = viewBoxMatch[1].split(/[\s,]+/);
|
|
22458
|
-
if (parts.length >= 4) {
|
|
22459
|
-
if (isNaN(width)) width = parseFloat(parts[2]);
|
|
22460
|
-
if (isNaN(height)) height = parseFloat(parts[3]);
|
|
22461
|
-
}
|
|
22462
|
-
}
|
|
22463
|
-
if (isNaN(width)) width = 300;
|
|
22464
|
-
if (isNaN(height)) height = 150;
|
|
22465
|
-
return { width, height };
|
|
22466
|
-
}
|
|
22467
|
-
function svgUsesExternalFonts(svgData) {
|
|
22468
|
-
const svg = svgData.toString("utf-8");
|
|
22469
|
-
return /<text[\s>]/i.test(svg) || /font-family/i.test(svg) || /@font-face/i.test(svg);
|
|
22470
|
-
}
|
|
22471
|
-
var MAX_RENDER_DIM = 4e3;
|
|
22472
|
-
async function renderSvgToPng(svgString) {
|
|
22473
|
-
await ensureRenderInit();
|
|
22474
|
-
const { width, height } = parseSvgDimensions(svgString);
|
|
22475
|
-
const maxDim = Math.max(width, height);
|
|
22476
|
-
const scale = Math.min(10, MAX_RENDER_DIM / maxDim);
|
|
22477
|
-
const targetWidth = Math.round(width * scale);
|
|
22478
|
-
const resvg = new ResvgClass(svgString, {
|
|
22479
|
-
fitTo: { mode: "width", value: targetWidth },
|
|
22480
|
-
font: {
|
|
22481
|
-
loadSystemFonts: false,
|
|
22482
|
-
fontBuffers,
|
|
22483
|
-
defaultFontFamily: "Helvetica"
|
|
22484
|
-
}
|
|
22485
|
-
});
|
|
22486
|
-
const rendered = resvg.render();
|
|
22487
|
-
const png = Buffer.from(rendered.asPng());
|
|
22488
|
-
rendered.free();
|
|
22489
|
-
resvg.free();
|
|
22490
|
-
return png;
|
|
22491
|
-
}
|
|
22492
|
-
|
|
22493
|
-
// src/git/svg-rasterize.ts
|
|
22494
|
-
var RENDER_TIMEOUT_MS = 15e3;
|
|
22495
|
-
var worker = null;
|
|
22496
|
-
var workerReady = false;
|
|
22497
|
-
var workerDisabled = false;
|
|
22498
|
-
var nextJobId = 0;
|
|
22499
|
-
var pending = /* @__PURE__ */ new Map();
|
|
22500
|
-
function resolveWorkerUrl() {
|
|
22501
|
-
return import.meta.url.endsWith(".ts") ? new URL("./svg-rasterize-worker-boot.mjs", import.meta.url) : new URL("./svg-rasterize-worker.js", import.meta.url);
|
|
22502
|
-
}
|
|
22503
|
-
function clearJobTimer(job) {
|
|
22504
|
-
if (job.timer !== void 0) clearTimeout(job.timer);
|
|
22505
|
-
}
|
|
22506
|
-
function fallbackAllPending() {
|
|
22507
|
-
for (const [id, job] of pending) {
|
|
22508
|
-
pending.delete(id);
|
|
22509
|
-
clearJobTimer(job);
|
|
22510
|
-
renderSvgToPng(job.svg).then(job.resolve, job.reject);
|
|
22511
|
-
}
|
|
22512
|
-
}
|
|
22513
|
-
function rejectAllPending(err) {
|
|
22514
|
-
for (const [id, job] of pending) {
|
|
22515
|
-
pending.delete(id);
|
|
22516
|
-
clearJobTimer(job);
|
|
22517
|
-
job.reject(err);
|
|
22518
|
-
}
|
|
22519
|
-
}
|
|
22520
|
-
function disposeWorker() {
|
|
22521
|
-
if (worker) {
|
|
22522
|
-
worker.removeAllListeners();
|
|
22523
|
-
void worker.terminate();
|
|
22524
|
-
}
|
|
22525
|
-
worker = null;
|
|
22526
|
-
workerReady = false;
|
|
22527
|
-
}
|
|
22528
|
-
function onJobTimeout(id) {
|
|
22529
|
-
const job = pending.get(id);
|
|
22530
|
-
if (!job) return;
|
|
22531
|
-
pending.delete(id);
|
|
22532
|
-
clearJobTimer(job);
|
|
22533
|
-
const requeue = [...pending.values()];
|
|
22534
|
-
pending.clear();
|
|
22535
|
-
for (const r of requeue) clearJobTimer(r);
|
|
22536
|
-
disposeWorker();
|
|
22537
|
-
job.reject(new Error(`SVG rasterization timed out after ${RENDER_TIMEOUT_MS} ms`));
|
|
22538
|
-
for (const r of requeue) submit(r);
|
|
22539
|
-
}
|
|
22540
|
-
function getWorker() {
|
|
22541
|
-
if (workerDisabled) return null;
|
|
22542
|
-
if (worker) return worker;
|
|
22543
|
-
let spawned;
|
|
22544
|
-
try {
|
|
22545
|
-
spawned = new Worker(resolveWorkerUrl());
|
|
22546
|
-
} catch {
|
|
22547
|
-
workerDisabled = true;
|
|
22548
|
-
return null;
|
|
22549
|
-
}
|
|
22550
|
-
spawned.on("message", (msg) => {
|
|
22551
|
-
if (msg.type === "ready") {
|
|
22552
|
-
workerReady = true;
|
|
22553
|
-
return;
|
|
22554
|
-
}
|
|
22555
|
-
const job = pending.get(msg.id);
|
|
22556
|
-
if (!job) return;
|
|
22557
|
-
pending.delete(msg.id);
|
|
22558
|
-
clearJobTimer(job);
|
|
22559
|
-
if (msg.type === "result") job.resolve(Buffer.from(msg.png));
|
|
22560
|
-
else job.reject(new Error(msg.message));
|
|
22561
|
-
});
|
|
22562
|
-
const handleFailure = (err) => {
|
|
22563
|
-
const startupFailure = !workerReady;
|
|
22564
|
-
disposeWorker();
|
|
22565
|
-
if (startupFailure) {
|
|
22566
|
-
workerDisabled = true;
|
|
22567
|
-
fallbackAllPending();
|
|
22568
|
-
} else {
|
|
22569
|
-
rejectAllPending(err);
|
|
22570
|
-
}
|
|
22571
|
-
};
|
|
22572
|
-
spawned.on("error", handleFailure);
|
|
22573
|
-
spawned.on("exit", (code) => {
|
|
22574
|
-
if (code !== 0) handleFailure(new Error(`SVG rasterization worker exited with code ${code}`));
|
|
22575
|
-
});
|
|
22576
|
-
worker = spawned;
|
|
22577
|
-
return spawned;
|
|
22578
|
-
}
|
|
22579
|
-
function submit(job) {
|
|
22580
|
-
const activeWorker = getWorker();
|
|
22581
|
-
if (!activeWorker) {
|
|
22582
|
-
renderSvgToPng(job.svg).then(job.resolve, job.reject);
|
|
22583
|
-
return;
|
|
22584
|
-
}
|
|
22585
|
-
const id = nextJobId++;
|
|
22586
|
-
job.timer = setTimeout(() => {
|
|
22587
|
-
onJobTimeout(id);
|
|
22588
|
-
}, RENDER_TIMEOUT_MS);
|
|
22589
|
-
pending.set(id, job);
|
|
22590
|
-
activeWorker.postMessage({ id, svg: job.svg });
|
|
22591
|
-
}
|
|
22592
|
-
async function rasterizeSvg(svgData) {
|
|
22593
|
-
const svg = svgData.toString("utf-8");
|
|
22594
|
-
return new Promise((resolve11, reject) => {
|
|
22595
|
-
submit({ svg, resolve: resolve11, reject });
|
|
22596
|
-
});
|
|
22597
|
-
}
|
|
22598
|
-
|
|
22599
22475
|
// src/routes/api/image.ts
|
|
22476
|
+
init_image_blobs();
|
|
22600
22477
|
var imageRoutes = new Hono7();
|
|
22601
|
-
function
|
|
22478
|
+
function blobImageSide(fileId, side) {
|
|
22602
22479
|
const dataDir = getDataDir();
|
|
22603
22480
|
if (dataDir === null) return null;
|
|
22604
|
-
const bytes =
|
|
22481
|
+
const bytes = readImageBlob(dataDir, fileId, side);
|
|
22605
22482
|
return bytes !== null ? { data: bytes, size: bytes.length } : null;
|
|
22606
22483
|
}
|
|
22484
|
+
function resolveImageSide(fileId, side, status, mode, filePath, oldPath, repoRoot, isDifftool) {
|
|
22485
|
+
if (side === "old" && status === "added") return null;
|
|
22486
|
+
if (side === "new" && status === "deleted") return null;
|
|
22487
|
+
if (isDifftool) return blobImageSide(fileId, side);
|
|
22488
|
+
const fromGit = side === "old" ? getOldImage(mode, filePath, oldPath, repoRoot) : getNewImage(mode, filePath, repoRoot);
|
|
22489
|
+
return fromGit ?? blobImageSide(fileId, side);
|
|
22490
|
+
}
|
|
22607
22491
|
imageRoutes.get("/image/:fileId/metadata", async (c) => {
|
|
22608
22492
|
const fileIdParam = requirePathParam(c, "fileId");
|
|
22609
22493
|
if (!fileIdParam.ok) return fileIdParam.response;
|
|
@@ -22617,8 +22501,8 @@ imageRoutes.get("/image/:fileId/metadata", async (c) => {
|
|
|
22617
22501
|
const oldPath = diff?.oldPath ?? null;
|
|
22618
22502
|
const status = diff?.status ?? "modified";
|
|
22619
22503
|
const isDifftool = review.mode === "difftool";
|
|
22620
|
-
const oldImage =
|
|
22621
|
-
const newImage =
|
|
22504
|
+
const oldImage = resolveImageSide(fileIdParam.data, "old", status, mode, file2.file_path, oldPath, repoRoot, isDifftool);
|
|
22505
|
+
const newImage = resolveImageSide(fileIdParam.data, "new", status, mode, file2.file_path, oldPath, repoRoot, isDifftool);
|
|
22622
22506
|
const oldMeta = oldImage !== null ? extractMetadata(oldImage.data, oldPath ?? file2.file_path) : null;
|
|
22623
22507
|
const newMeta = newImage !== null ? extractMetadata(newImage.data, file2.file_path) : null;
|
|
22624
22508
|
return c.json({
|
|
@@ -22639,19 +22523,19 @@ imageRoutes.get("/image/:fileId/:side", async (c) => {
|
|
|
22639
22523
|
const mode = parseModeString(review.mode);
|
|
22640
22524
|
const diff = parseDiffData(file2.diff_data);
|
|
22641
22525
|
const oldPath = diff?.oldPath ?? null;
|
|
22642
|
-
const
|
|
22526
|
+
const status = diff?.status ?? "modified";
|
|
22527
|
+
const image = resolveImageSide(
|
|
22528
|
+
fileIdParam.data,
|
|
22529
|
+
side,
|
|
22530
|
+
status,
|
|
22531
|
+
mode,
|
|
22532
|
+
file2.file_path,
|
|
22533
|
+
oldPath,
|
|
22534
|
+
repoRoot,
|
|
22535
|
+
review.mode === "difftool"
|
|
22536
|
+
);
|
|
22643
22537
|
if (!image) return c.text("Image not available", 404);
|
|
22644
22538
|
const sidePath = side === "old" ? oldPath ?? file2.file_path : file2.file_path;
|
|
22645
|
-
if (isSvgFile(sidePath)) {
|
|
22646
|
-
try {
|
|
22647
|
-
const png = await rasterizeSvg(image.data);
|
|
22648
|
-
return new Response(new Uint8Array(png), {
|
|
22649
|
-
headers: { "Content-Type": "image/png", "Cache-Control": "no-cache" }
|
|
22650
|
-
});
|
|
22651
|
-
} catch {
|
|
22652
|
-
return c.text("SVG rasterization failed", 500);
|
|
22653
|
-
}
|
|
22654
|
-
}
|
|
22655
22539
|
const contentType = getContentType(sidePath);
|
|
22656
22540
|
return new Response(new Uint8Array(image.data), {
|
|
22657
22541
|
headers: { "Content-Type": contentType, "Cache-Control": "no-cache" }
|
|
@@ -22661,7 +22545,7 @@ imageRoutes.get("/image/:fileId/:side", async (c) => {
|
|
|
22661
22545
|
// src/routes/api/outline.ts
|
|
22662
22546
|
init_queries();
|
|
22663
22547
|
import { spawnSync as spawnSync8 } from "child_process";
|
|
22664
|
-
import { readFileSync as
|
|
22548
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
22665
22549
|
import { Hono as Hono8 } from "hono";
|
|
22666
22550
|
import { resolve as resolve6 } from "path";
|
|
22667
22551
|
|
|
@@ -23039,7 +22923,7 @@ outlineRoutes.get("/symbol-definition", async (c) => {
|
|
|
23039
22923
|
}
|
|
23040
22924
|
let content = "";
|
|
23041
22925
|
try {
|
|
23042
|
-
content =
|
|
22926
|
+
content = readFileSync9(resolve6(repoRoot, filePath), "utf-8");
|
|
23043
22927
|
} catch {
|
|
23044
22928
|
continue;
|
|
23045
22929
|
}
|
|
@@ -23075,15 +22959,15 @@ function collectDefinitions(symbols, targetName, fileId, filePath, out) {
|
|
|
23075
22959
|
}
|
|
23076
22960
|
|
|
23077
22961
|
// src/routes/api/project-settings.ts
|
|
23078
|
-
import { existsSync as
|
|
22962
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
|
|
23079
22963
|
import { Hono as Hono9 } from "hono";
|
|
23080
|
-
import { join as
|
|
22964
|
+
import { join as join10 } from "path";
|
|
23081
22965
|
var projectSettingsRoutes = new Hono9();
|
|
23082
22966
|
function readProjectSettings(repoRoot) {
|
|
23083
|
-
const settingsPath =
|
|
22967
|
+
const settingsPath = join10(repoRoot, ".glassbox", "settings.json");
|
|
23084
22968
|
try {
|
|
23085
|
-
if (
|
|
23086
|
-
const raw2 = JSON.parse(
|
|
22969
|
+
if (existsSync8(settingsPath)) {
|
|
22970
|
+
const raw2 = JSON.parse(readFileSync10(settingsPath, "utf-8"));
|
|
23087
22971
|
const parsed = ProjectSettingsSchema.safeParse(raw2);
|
|
23088
22972
|
if (parsed.success) return parsed.data;
|
|
23089
22973
|
}
|
|
@@ -23092,9 +22976,9 @@ function readProjectSettings(repoRoot) {
|
|
|
23092
22976
|
return {};
|
|
23093
22977
|
}
|
|
23094
22978
|
function writeProjectSettings(repoRoot, settings) {
|
|
23095
|
-
const dir =
|
|
22979
|
+
const dir = join10(repoRoot, ".glassbox");
|
|
23096
22980
|
mkdirSync7(dir, { recursive: true });
|
|
23097
|
-
writeFileSync8(
|
|
22981
|
+
writeFileSync8(join10(dir, "settings.json"), JSON.stringify(settings, null, 2), "utf-8");
|
|
23098
22982
|
}
|
|
23099
22983
|
projectSettingsRoutes.get("/project-settings", (c) => {
|
|
23100
22984
|
const repoRoot = c.get("repoRoot");
|
|
@@ -23112,7 +22996,7 @@ projectSettingsRoutes.patch("/project-settings", async (c) => {
|
|
|
23112
22996
|
|
|
23113
22997
|
// src/routes/api/review-notes.ts
|
|
23114
22998
|
init_store();
|
|
23115
|
-
import { readFileSync as
|
|
22999
|
+
import { readFileSync as readFileSync11, statSync as statSync3 } from "fs";
|
|
23116
23000
|
import { Hono as Hono10 } from "hono";
|
|
23117
23001
|
import { extname, relative, resolve as resolve7 } from "path";
|
|
23118
23002
|
var reviewNotesRoutes = new Hono10();
|
|
@@ -23139,7 +23023,7 @@ reviewNotesRoutes.get("/review-notes/artifact", (c) => {
|
|
|
23139
23023
|
try {
|
|
23140
23024
|
const stat = statSync3(abs);
|
|
23141
23025
|
if (!stat.isFile() || stat.size > ARTIFACT_SERVE_MAX_BYTES) return c.text("Not found", 404);
|
|
23142
|
-
const body =
|
|
23026
|
+
const body = readFileSync11(abs);
|
|
23143
23027
|
return c.body(body, 200, { "Content-Type": contentType });
|
|
23144
23028
|
} catch {
|
|
23145
23029
|
return c.text("Not found", 404);
|
|
@@ -23316,13 +23200,13 @@ apiRoutes.route("/", systemRoutes);
|
|
|
23316
23200
|
import { spawnSync as spawnSync9 } from "child_process";
|
|
23317
23201
|
import { mkdirSync as mkdirSync8 } from "fs";
|
|
23318
23202
|
import { Hono as Hono15 } from "hono";
|
|
23319
|
-
import { join as
|
|
23203
|
+
import { join as join11 } from "path";
|
|
23320
23204
|
var channelApiRoutes = new Hono15();
|
|
23321
23205
|
channelApiRoutes.get("/status", async (c) => {
|
|
23322
23206
|
const config2 = readGlobalConfig();
|
|
23323
23207
|
const enabled = config2.channelEnabled === true;
|
|
23324
23208
|
const repoRoot = c.get("repoRoot");
|
|
23325
|
-
const dataDir =
|
|
23209
|
+
const dataDir = join11(repoRoot, ".glassbox");
|
|
23326
23210
|
const connected = enabled ? await isChannelAlive(dataDir) : false;
|
|
23327
23211
|
return c.json({ enabled, connected });
|
|
23328
23212
|
});
|
|
@@ -23331,7 +23215,7 @@ channelApiRoutes.post("/enable", (c) => {
|
|
|
23331
23215
|
config2.channelEnabled = true;
|
|
23332
23216
|
});
|
|
23333
23217
|
const repoRoot = c.get("repoRoot");
|
|
23334
|
-
const dataDir =
|
|
23218
|
+
const dataDir = join11(repoRoot, ".glassbox");
|
|
23335
23219
|
mkdirSync8(dataDir, { recursive: true });
|
|
23336
23220
|
registerChannel(dataDir);
|
|
23337
23221
|
return c.json({ ok: true });
|
|
@@ -23341,7 +23225,7 @@ channelApiRoutes.post("/disable", (c) => {
|
|
|
23341
23225
|
config2.channelEnabled = false;
|
|
23342
23226
|
});
|
|
23343
23227
|
const repoRoot = c.get("repoRoot");
|
|
23344
|
-
const dataDir =
|
|
23228
|
+
const dataDir = join11(repoRoot, ".glassbox");
|
|
23345
23229
|
unregisterChannel(dataDir);
|
|
23346
23230
|
return c.json({ ok: true });
|
|
23347
23231
|
});
|
|
@@ -23349,7 +23233,7 @@ channelApiRoutes.post("/trigger", async (c) => {
|
|
|
23349
23233
|
const parsed = await parseBody(c, TriggerChannelReqSchema);
|
|
23350
23234
|
if (!parsed.ok) return parsed.response;
|
|
23351
23235
|
const repoRoot = c.get("repoRoot");
|
|
23352
|
-
const dataDir =
|
|
23236
|
+
const dataDir = join11(repoRoot, ".glassbox");
|
|
23353
23237
|
const sent = await triggerChannel(dataDir, parsed.data.message);
|
|
23354
23238
|
if (!sent) {
|
|
23355
23239
|
return c.json({ error: "Channel not connected" }, 503);
|
|
@@ -23380,9 +23264,9 @@ channelApiRoutes.get("/claude-check", (c) => {
|
|
|
23380
23264
|
import { Hono as Hono16 } from "hono";
|
|
23381
23265
|
init_connection();
|
|
23382
23266
|
init_queries();
|
|
23383
|
-
init_blob_store();
|
|
23384
23267
|
init_session();
|
|
23385
23268
|
init_difftool();
|
|
23269
|
+
init_image_blobs();
|
|
23386
23270
|
var difftoolApiRoutes = new Hono16();
|
|
23387
23271
|
difftoolApiRoutes.get("/status", (c) => {
|
|
23388
23272
|
return c.json(getDifftoolStatus("global"));
|
|
@@ -23423,8 +23307,8 @@ difftoolApiRoutes.post("/append", async (c) => {
|
|
|
23423
23307
|
if (diff.isBinary || isSvgFile(diff.filePath)) {
|
|
23424
23308
|
const dataDir = getDataDir();
|
|
23425
23309
|
if (dataDir !== null) {
|
|
23426
|
-
|
|
23427
|
-
|
|
23310
|
+
writeImageBlob(dataDir, fileId, "old", oldContent);
|
|
23311
|
+
writeImageBlob(dataDir, fileId, "new", newContent);
|
|
23428
23312
|
}
|
|
23429
23313
|
}
|
|
23430
23314
|
return c.json({ ok: true, fileId });
|
|
@@ -23460,7 +23344,7 @@ difftoolApiRoutes.post("/end", (c) => {
|
|
|
23460
23344
|
});
|
|
23461
23345
|
|
|
23462
23346
|
// src/routes/pages.tsx
|
|
23463
|
-
import { readFileSync as
|
|
23347
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
23464
23348
|
import { Hono as Hono17 } from "hono";
|
|
23465
23349
|
import { resolve as resolve8 } from "path";
|
|
23466
23350
|
|
|
@@ -23645,25 +23529,31 @@ function ImageDiff({ file: file2, diff, fontWarning, baseWidth, baseHeight }) {
|
|
|
23645
23529
|
/* @__PURE__ */ jsx2("div", { className: "image-diff-panel image-diff-metadata", "data-panel": "metadata", children: /* @__PURE__ */ jsx2("div", { className: "image-metadata-loading", children: "Loading metadata..." }) }),
|
|
23646
23530
|
hasComparison && /* @__PURE__ */ jsx2("div", { className: "image-diff-panel image-diff-visual", "data-panel": "difference", children: /* @__PURE__ */ jsx2("div", { className: "image-visual-canvas", "data-zoomable": "true", children: /* @__PURE__ */ jsxs2("div", { className: "image-zoom-wrap", children: [
|
|
23647
23531
|
/* @__PURE__ */ jsx2("img", { className: "image-layer image-layer-old", src: `/api/image/${fileId}/old`, alt: "Old version" }),
|
|
23648
|
-
/* @__PURE__ */ jsx2("img", { className: "image-layer image-layer-new image-blend", src: `/api/image/${fileId}/new`, alt: "New version" })
|
|
23532
|
+
/* @__PURE__ */ jsx2("img", { className: "image-layer image-layer-new image-blend", src: `/api/image/${fileId}/new`, alt: "New version" }),
|
|
23533
|
+
/* @__PURE__ */ jsx2("div", { className: "region-overlay", "data-region-overlay": true })
|
|
23649
23534
|
] }) }) }),
|
|
23650
23535
|
hasComparison && /* @__PURE__ */ jsx2("div", { className: "image-diff-panel image-diff-visual", "data-panel": "slice", children: /* @__PURE__ */ jsxs2("div", { className: "image-visual-canvas", "data-zoomable": "true", children: [
|
|
23651
23536
|
/* @__PURE__ */ jsxs2("div", { className: "image-zoom-wrap", children: [
|
|
23652
23537
|
/* @__PURE__ */ jsx2("img", { className: "image-layer image-layer-old", src: `/api/image/${fileId}/old`, alt: "Old version" }),
|
|
23653
|
-
/* @__PURE__ */ jsx2("img", { className: "image-layer image-layer-new image-slice-clipped", src: `/api/image/${fileId}/new`, alt: "New version" })
|
|
23538
|
+
/* @__PURE__ */ jsx2("img", { className: "image-layer image-layer-new image-slice-clipped", src: `/api/image/${fileId}/new`, alt: "New version" }),
|
|
23539
|
+
/* @__PURE__ */ jsx2("div", { className: "region-overlay", "data-region-overlay": true })
|
|
23654
23540
|
] }),
|
|
23655
23541
|
/* @__PURE__ */ jsx2("div", { className: "slice-line" }),
|
|
23656
23542
|
/* @__PURE__ */ jsx2("div", { className: "slice-handle slice-handle-a" }),
|
|
23657
23543
|
/* @__PURE__ */ jsx2("div", { className: "slice-handle slice-handle-b" })
|
|
23658
23544
|
] }) }),
|
|
23659
|
-
!hasComparison && /* @__PURE__ */ jsx2("div", { className: "image-diff-panel image-diff-visual", "data-panel": "image", children: /* @__PURE__ */ jsx2("div", { className: "image-visual-canvas", "data-zoomable": "true", children: /* @__PURE__ */
|
|
23660
|
-
|
|
23661
|
-
|
|
23662
|
-
|
|
23663
|
-
|
|
23664
|
-
|
|
23665
|
-
|
|
23666
|
-
|
|
23545
|
+
!hasComparison && /* @__PURE__ */ jsx2("div", { className: "image-diff-panel image-diff-visual", "data-panel": "image", children: /* @__PURE__ */ jsx2("div", { className: "image-visual-canvas", "data-zoomable": "true", children: /* @__PURE__ */ jsxs2("div", { className: "image-zoom-wrap", children: [
|
|
23546
|
+
/* @__PURE__ */ jsx2(
|
|
23547
|
+
"img",
|
|
23548
|
+
{
|
|
23549
|
+
className: "image-layer image-layer-old",
|
|
23550
|
+
src: `/api/image/${fileId}/${isAdded ? "new" : "old"}`,
|
|
23551
|
+
alt: isAdded ? "New image" : "Deleted image"
|
|
23552
|
+
}
|
|
23553
|
+
),
|
|
23554
|
+
/* @__PURE__ */ jsx2("div", { className: "region-overlay", "data-region-overlay": true })
|
|
23555
|
+
] }) }) }),
|
|
23556
|
+
/* @__PURE__ */ jsx2("div", { className: "image-feedback", "data-image-feedback": true })
|
|
23667
23557
|
]
|
|
23668
23558
|
}
|
|
23669
23559
|
);
|
|
@@ -24087,9 +23977,9 @@ function AnnotationRows({ annotations }) {
|
|
|
24087
23977
|
}
|
|
24088
23978
|
|
|
24089
23979
|
// src/themes/config.ts
|
|
24090
|
-
import { existsSync as
|
|
24091
|
-
import { join as
|
|
24092
|
-
var THEMES_DIR =
|
|
23980
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync9, readdirSync as readdirSync2, readFileSync as readFileSync12, unlinkSync as unlinkSync3, writeFileSync as writeFileSync9 } from "fs";
|
|
23981
|
+
import { join as join12 } from "path";
|
|
23982
|
+
var THEMES_DIR = join12(GLOBAL_CONFIG_DIR, "themes");
|
|
24093
23983
|
function getActiveThemeId() {
|
|
24094
23984
|
const config2 = readGlobalConfig();
|
|
24095
23985
|
const theme = config2.theme;
|
|
@@ -24103,13 +23993,13 @@ function setActiveThemeId(id) {
|
|
|
24103
23993
|
});
|
|
24104
23994
|
}
|
|
24105
23995
|
function loadCustomThemes() {
|
|
24106
|
-
if (!
|
|
23996
|
+
if (!existsSync9(THEMES_DIR)) return [];
|
|
24107
23997
|
const themes = [];
|
|
24108
23998
|
try {
|
|
24109
23999
|
const files = readdirSync2(THEMES_DIR).filter((f) => f.endsWith(".json"));
|
|
24110
24000
|
for (const file2 of files) {
|
|
24111
24001
|
try {
|
|
24112
|
-
const parsed = StoredCustomThemeSchema.safeParse(JSON.parse(
|
|
24002
|
+
const parsed = StoredCustomThemeSchema.safeParse(JSON.parse(readFileSync12(join12(THEMES_DIR, file2), "utf-8")));
|
|
24113
24003
|
if (!parsed.success) continue;
|
|
24114
24004
|
const d = parsed.data;
|
|
24115
24005
|
themes.push({ id: d.id, name: d.name, colors: d.colors, builtIn: false, baseTheme: d.baseTheme ?? "" });
|
|
@@ -24122,20 +24012,20 @@ function loadCustomThemes() {
|
|
|
24122
24012
|
}
|
|
24123
24013
|
function saveCustomTheme(theme) {
|
|
24124
24014
|
mkdirSync9(THEMES_DIR, { recursive: true });
|
|
24125
|
-
const filePath =
|
|
24015
|
+
const filePath = join12(THEMES_DIR, `${theme.id}.json`);
|
|
24126
24016
|
writeFileSync9(filePath, JSON.stringify(theme, null, 2), "utf-8");
|
|
24127
24017
|
}
|
|
24128
24018
|
function deleteCustomTheme(id) {
|
|
24129
|
-
const filePath =
|
|
24130
|
-
if (
|
|
24019
|
+
const filePath = join12(THEMES_DIR, `${id}.json`);
|
|
24020
|
+
if (existsSync9(filePath)) {
|
|
24131
24021
|
unlinkSync3(filePath);
|
|
24132
24022
|
}
|
|
24133
24023
|
}
|
|
24134
24024
|
function getCustomTheme(id) {
|
|
24135
|
-
const filePath =
|
|
24136
|
-
if (!
|
|
24025
|
+
const filePath = join12(THEMES_DIR, `${id}.json`);
|
|
24026
|
+
if (!existsSync9(filePath)) return void 0;
|
|
24137
24027
|
try {
|
|
24138
|
-
const parsed = StoredCustomThemeSchema.safeParse(JSON.parse(
|
|
24028
|
+
const parsed = StoredCustomThemeSchema.safeParse(JSON.parse(readFileSync12(filePath, "utf-8")));
|
|
24139
24029
|
if (!parsed.success) return void 0;
|
|
24140
24030
|
const d = parsed.data;
|
|
24141
24031
|
return { id: d.id, name: d.name, colors: d.colors, builtIn: false, baseTheme: d.baseTheme ?? "" };
|
|
@@ -24435,6 +24325,29 @@ function ReviewShell({ reviewId, review, files, annotationCounts, staleCounts, f
|
|
|
24435
24325
|
// src/routes/pages.tsx
|
|
24436
24326
|
init_queries();
|
|
24437
24327
|
|
|
24328
|
+
// src/git/svg-meta.ts
|
|
24329
|
+
function parseSvgDimensions(svg) {
|
|
24330
|
+
const widthMatch = svg.match(/\bwidth\s*=\s*["']([^"']+)["']/);
|
|
24331
|
+
const heightMatch = svg.match(/\bheight\s*=\s*["']([^"']+)["']/);
|
|
24332
|
+
const viewBoxMatch = svg.match(/\bviewBox\s*=\s*["']([^"']+)["']/);
|
|
24333
|
+
let width = widthMatch ? parseFloat(widthMatch[1]) : NaN;
|
|
24334
|
+
let height = heightMatch ? parseFloat(heightMatch[1]) : NaN;
|
|
24335
|
+
if ((isNaN(width) || isNaN(height)) && viewBoxMatch) {
|
|
24336
|
+
const parts = viewBoxMatch[1].split(/[\s,]+/);
|
|
24337
|
+
if (parts.length >= 4) {
|
|
24338
|
+
if (isNaN(width)) width = parseFloat(parts[2]);
|
|
24339
|
+
if (isNaN(height)) height = parseFloat(parts[3]);
|
|
24340
|
+
}
|
|
24341
|
+
}
|
|
24342
|
+
if (isNaN(width)) width = 300;
|
|
24343
|
+
if (isNaN(height)) height = 150;
|
|
24344
|
+
return { width, height };
|
|
24345
|
+
}
|
|
24346
|
+
function svgUsesExternalFonts(svgData) {
|
|
24347
|
+
const svg = svgData.toString("utf-8");
|
|
24348
|
+
return /<text[\s>]/i.test(svg) || /font-family/i.test(svg) || /@font-face/i.test(svg);
|
|
24349
|
+
}
|
|
24350
|
+
|
|
24438
24351
|
// src/review-notes/reanchor.ts
|
|
24439
24352
|
var MATCH_RADIUS = 50;
|
|
24440
24353
|
function reanchorReviewNotes(notes, diff) {
|
|
@@ -24558,7 +24471,7 @@ pageRoutes.get("/file-raw", (c) => {
|
|
|
24558
24471
|
const repoRoot = c.get("repoRoot");
|
|
24559
24472
|
let content;
|
|
24560
24473
|
try {
|
|
24561
|
-
content =
|
|
24474
|
+
content = readFileSync13(resolve8(repoRoot, filePath), "utf-8");
|
|
24562
24475
|
} catch {
|
|
24563
24476
|
return c.text("File not found", 404);
|
|
24564
24477
|
}
|
|
@@ -24748,17 +24661,17 @@ async function startServer(port, reviewId, repoRoot, options) {
|
|
|
24748
24661
|
await next();
|
|
24749
24662
|
});
|
|
24750
24663
|
const selfDir = dirname4(fileURLToPath2(import.meta.url));
|
|
24751
|
-
const distDir =
|
|
24664
|
+
const distDir = existsSync10(join13(selfDir, "client", "styles.css")) ? join13(selfDir, "client") : join13(selfDir, "..", "dist", "client");
|
|
24752
24665
|
app.get("/static/styles.css", (c) => {
|
|
24753
|
-
const css =
|
|
24666
|
+
const css = readFileSync14(join13(distDir, "styles.css"), "utf-8");
|
|
24754
24667
|
return c.text(css, 200, { "Content-Type": "text/css", "Cache-Control": "no-cache" });
|
|
24755
24668
|
});
|
|
24756
24669
|
app.get("/static/app.js", (c) => {
|
|
24757
|
-
const js =
|
|
24670
|
+
const js = readFileSync14(join13(distDir, "app.global.js"), "utf-8");
|
|
24758
24671
|
return c.text(js, 200, { "Content-Type": "application/javascript", "Cache-Control": "no-cache" });
|
|
24759
24672
|
});
|
|
24760
24673
|
app.get("/static/history.js", (c) => {
|
|
24761
|
-
const js =
|
|
24674
|
+
const js = readFileSync14(join13(distDir, "history.global.js"), "utf-8");
|
|
24762
24675
|
return c.text(js, 200, { "Content-Type": "application/javascript", "Cache-Control": "no-cache" });
|
|
24763
24676
|
});
|
|
24764
24677
|
app.get("/favicon.ico", (c) => c.body(null, 204));
|
|
@@ -24796,7 +24709,7 @@ async function startServer(port, reviewId, repoRoot, options) {
|
|
|
24796
24709
|
try {
|
|
24797
24710
|
const globalConfig2 = readGlobalConfig();
|
|
24798
24711
|
if (globalConfig2.channelEnabled === true) {
|
|
24799
|
-
const dataDir =
|
|
24712
|
+
const dataDir = join13(repoRoot, ".glassbox");
|
|
24800
24713
|
registerChannel(dataDir);
|
|
24801
24714
|
}
|
|
24802
24715
|
} catch {
|
|
@@ -24811,8 +24724,8 @@ async function startServer(port, reviewId, repoRoot, options) {
|
|
|
24811
24724
|
}
|
|
24812
24725
|
|
|
24813
24726
|
// src/skills.ts
|
|
24814
|
-
import { existsSync as
|
|
24815
|
-
import { join as
|
|
24727
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync10, readFileSync as readFileSync15, writeFileSync as writeFileSync10 } from "fs";
|
|
24728
|
+
import { join as join14 } from "path";
|
|
24816
24729
|
var SKILL_VERSION = 1;
|
|
24817
24730
|
function versionHeader() {
|
|
24818
24731
|
return `<!-- glassbox-skill-version: ${SKILL_VERSION} -->`;
|
|
@@ -24823,8 +24736,8 @@ function parseVersionHeader(content) {
|
|
|
24823
24736
|
return parseInt(match[1], 10);
|
|
24824
24737
|
}
|
|
24825
24738
|
function updateFile(path, content) {
|
|
24826
|
-
if (
|
|
24827
|
-
const existing =
|
|
24739
|
+
if (existsSync11(path)) {
|
|
24740
|
+
const existing = readFileSync15(path, "utf-8");
|
|
24828
24741
|
const version2 = parseVersionHeader(existing);
|
|
24829
24742
|
if (version2 !== null && version2 >= SKILL_VERSION) {
|
|
24830
24743
|
return false;
|
|
@@ -24850,7 +24763,7 @@ function skillBody() {
|
|
|
24850
24763
|
].join("\n");
|
|
24851
24764
|
}
|
|
24852
24765
|
function ensureClaudeSkills(cwd) {
|
|
24853
|
-
const dir =
|
|
24766
|
+
const dir = join14(cwd, ".claude", "skills", "glassbox");
|
|
24854
24767
|
mkdirSync10(dir, { recursive: true });
|
|
24855
24768
|
const content = [
|
|
24856
24769
|
"---",
|
|
@@ -24863,10 +24776,10 @@ function ensureClaudeSkills(cwd) {
|
|
|
24863
24776
|
skillBody(),
|
|
24864
24777
|
""
|
|
24865
24778
|
].join("\n");
|
|
24866
|
-
return updateFile(
|
|
24779
|
+
return updateFile(join14(dir, "SKILL.md"), content);
|
|
24867
24780
|
}
|
|
24868
24781
|
function ensureCursorRules(cwd) {
|
|
24869
|
-
const rulesDir =
|
|
24782
|
+
const rulesDir = join14(cwd, ".cursor", "rules");
|
|
24870
24783
|
mkdirSync10(rulesDir, { recursive: true });
|
|
24871
24784
|
const content = [
|
|
24872
24785
|
"---",
|
|
@@ -24878,10 +24791,10 @@ function ensureCursorRules(cwd) {
|
|
|
24878
24791
|
skillBody(),
|
|
24879
24792
|
""
|
|
24880
24793
|
].join("\n");
|
|
24881
|
-
return updateFile(
|
|
24794
|
+
return updateFile(join14(rulesDir, "glassbox.mdc"), content);
|
|
24882
24795
|
}
|
|
24883
24796
|
function ensureCopilotPrompts(cwd) {
|
|
24884
|
-
const promptsDir =
|
|
24797
|
+
const promptsDir = join14(cwd, ".github", "prompts");
|
|
24885
24798
|
mkdirSync10(promptsDir, { recursive: true });
|
|
24886
24799
|
const content = [
|
|
24887
24800
|
"---",
|
|
@@ -24892,10 +24805,10 @@ function ensureCopilotPrompts(cwd) {
|
|
|
24892
24805
|
skillBody(),
|
|
24893
24806
|
""
|
|
24894
24807
|
].join("\n");
|
|
24895
|
-
return updateFile(
|
|
24808
|
+
return updateFile(join14(promptsDir, "glassbox.prompt.md"), content);
|
|
24896
24809
|
}
|
|
24897
24810
|
function ensureWindsurfRules(cwd) {
|
|
24898
|
-
const rulesDir =
|
|
24811
|
+
const rulesDir = join14(cwd, ".windsurf", "rules");
|
|
24899
24812
|
mkdirSync10(rulesDir, { recursive: true });
|
|
24900
24813
|
const content = [
|
|
24901
24814
|
"---",
|
|
@@ -24907,21 +24820,21 @@ function ensureWindsurfRules(cwd) {
|
|
|
24907
24820
|
skillBody(),
|
|
24908
24821
|
""
|
|
24909
24822
|
].join("\n");
|
|
24910
|
-
return updateFile(
|
|
24823
|
+
return updateFile(join14(rulesDir, "glassbox.md"), content);
|
|
24911
24824
|
}
|
|
24912
24825
|
function ensureSkills() {
|
|
24913
24826
|
const cwd = process.cwd();
|
|
24914
24827
|
const platforms = [];
|
|
24915
|
-
if (
|
|
24828
|
+
if (existsSync11(join14(cwd, ".claude"))) {
|
|
24916
24829
|
if (ensureClaudeSkills(cwd)) platforms.push("Claude Code");
|
|
24917
24830
|
}
|
|
24918
|
-
if (
|
|
24831
|
+
if (existsSync11(join14(cwd, ".cursor"))) {
|
|
24919
24832
|
if (ensureCursorRules(cwd)) platforms.push("Cursor");
|
|
24920
24833
|
}
|
|
24921
|
-
if (
|
|
24834
|
+
if (existsSync11(join14(cwd, ".github", "prompts")) || existsSync11(join14(cwd, ".github", "copilot-instructions.md"))) {
|
|
24922
24835
|
if (ensureCopilotPrompts(cwd)) platforms.push("GitHub Copilot");
|
|
24923
24836
|
}
|
|
24924
|
-
if (
|
|
24837
|
+
if (existsSync11(join14(cwd, ".windsurf"))) {
|
|
24925
24838
|
if (ensureWindsurfRules(cwd)) platforms.push("Windsurf");
|
|
24926
24839
|
}
|
|
24927
24840
|
return platforms;
|
|
@@ -24929,19 +24842,19 @@ function ensureSkills() {
|
|
|
24929
24842
|
|
|
24930
24843
|
// src/update-check.ts
|
|
24931
24844
|
init_zod();
|
|
24932
|
-
import { existsSync as
|
|
24845
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync11, readFileSync as readFileSync16, writeFileSync as writeFileSync11 } from "fs";
|
|
24933
24846
|
import { get } from "https";
|
|
24934
24847
|
import { homedir as homedir3 } from "os";
|
|
24935
|
-
import { dirname as dirname5, join as
|
|
24848
|
+
import { dirname as dirname5, join as join15 } from "path";
|
|
24936
24849
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
24937
|
-
var DATA_DIR =
|
|
24938
|
-
var CHECK_FILE =
|
|
24850
|
+
var DATA_DIR = join15(homedir3(), ".glassbox");
|
|
24851
|
+
var CHECK_FILE = join15(DATA_DIR, "last-update-check");
|
|
24939
24852
|
var PACKAGE_NAME = "glassbox";
|
|
24940
24853
|
var VersionPayloadSchema = external_exports.object({ version: external_exports.string() });
|
|
24941
24854
|
function getCurrentVersion() {
|
|
24942
24855
|
try {
|
|
24943
24856
|
const dir = dirname5(fileURLToPath3(import.meta.url));
|
|
24944
|
-
const raw2 = JSON.parse(
|
|
24857
|
+
const raw2 = JSON.parse(readFileSync16(join15(dir, "..", "package.json"), "utf-8"));
|
|
24945
24858
|
return VersionPayloadSchema.parse(raw2).version;
|
|
24946
24859
|
} catch {
|
|
24947
24860
|
return "0.0.0";
|
|
@@ -24949,8 +24862,8 @@ function getCurrentVersion() {
|
|
|
24949
24862
|
}
|
|
24950
24863
|
function getLastCheckDate() {
|
|
24951
24864
|
try {
|
|
24952
|
-
if (
|
|
24953
|
-
return
|
|
24865
|
+
if (existsSync12(CHECK_FILE)) {
|
|
24866
|
+
return readFileSync16(CHECK_FILE, "utf-8").trim();
|
|
24954
24867
|
}
|
|
24955
24868
|
} catch {
|
|
24956
24869
|
}
|
|
@@ -25258,22 +25171,22 @@ async function main() {
|
|
|
25258
25171
|
console.log("AI service test mode enabled \u2014 using mock AI responses");
|
|
25259
25172
|
}
|
|
25260
25173
|
if (debug) {
|
|
25261
|
-
console.log(`[debug] Build timestamp: ${"2026-06-
|
|
25174
|
+
console.log(`[debug] Build timestamp: ${"2026-06-22T06:43:57.425Z"}`);
|
|
25262
25175
|
}
|
|
25263
25176
|
if (projectDir !== null) {
|
|
25264
25177
|
process.chdir(projectDir);
|
|
25265
25178
|
}
|
|
25266
25179
|
if (dataDir === null) {
|
|
25267
|
-
dataDir =
|
|
25180
|
+
dataDir = join17(process.cwd(), ".glassbox");
|
|
25268
25181
|
}
|
|
25269
25182
|
if (difftoolServe) {
|
|
25270
25183
|
const { initDifftoolSession: initDifftoolSession2 } = await Promise.resolve().then(() => (init_session(), session_exports));
|
|
25271
25184
|
const { writeDiscovery: writeDiscovery2, clearDiscovery: clearDiscovery2, releaseStartingLock: releaseStartingLock2 } = await Promise.resolve().then(() => (init_difftool_discovery(), difftool_discovery_exports));
|
|
25272
|
-
const {
|
|
25185
|
+
const { clearImageBlobs: clearImageBlobs2 } = await Promise.resolve().then(() => (init_image_blobs(), image_blobs_exports));
|
|
25273
25186
|
mkdirSync13(dataDir, { recursive: true });
|
|
25274
25187
|
setDataDir(dataDir);
|
|
25275
25188
|
const sessionDataDir = dataDir;
|
|
25276
|
-
|
|
25189
|
+
clearImageBlobs2(sessionDataDir);
|
|
25277
25190
|
const repoRoot2 = process.cwd();
|
|
25278
25191
|
const review2 = await createReview(repoRoot2, "git difftool", "difftool");
|
|
25279
25192
|
const { port: actualPort, server } = await startServer(port, review2.id, repoRoot2, { noOpen, strictPort });
|
|
@@ -25285,7 +25198,7 @@ async function main() {
|
|
|
25285
25198
|
server.close();
|
|
25286
25199
|
} catch {
|
|
25287
25200
|
}
|
|
25288
|
-
|
|
25201
|
+
clearImageBlobs2(sessionDataDir);
|
|
25289
25202
|
clearDiscovery2();
|
|
25290
25203
|
releaseStartingLock2();
|
|
25291
25204
|
process.exit(0);
|
|
@@ -25305,7 +25218,7 @@ async function main() {
|
|
|
25305
25218
|
}
|
|
25306
25219
|
process.exit(1);
|
|
25307
25220
|
}
|
|
25308
|
-
dataDir =
|
|
25221
|
+
dataDir = join17(tmpdir2(), `glassbox-demo-${demo}-${Date.now()}`);
|
|
25309
25222
|
setDemoMode(demo);
|
|
25310
25223
|
console.log(`
|
|
25311
25224
|
DEMO MODE: ${scenario.label}
|
|
@@ -25333,7 +25246,7 @@ async function main() {
|
|
|
25333
25246
|
if (mode.type === "diff") {
|
|
25334
25247
|
const { pathA, pathB } = mode;
|
|
25335
25248
|
for (const p of [pathA, pathB]) {
|
|
25336
|
-
if (!
|
|
25249
|
+
if (!existsSync14(p)) {
|
|
25337
25250
|
console.error(`Error: path does not exist: ${p}`);
|
|
25338
25251
|
process.exit(1);
|
|
25339
25252
|
}
|